New field_flag: free-tagging taxonomy reference (multi-value, auto-create) on ligne_comptable, for marking a line with an unstructured problem description (e.g. "client impayé") that can't be detected automatically the way the répartition écart already is. - New "Signalement" column, inline-editable like Client/Facture/Libellé (comma-separated tags, datalist autocomplete, server-side auto-create of unknown tags -- same pattern LedgerActionsController already used for Client, now shared via findOrCreateTerm()). - New "Signalées uniquement" filter, mirroring "Écarts uniquement". - Flagged rows get a distinct amber left-edge accent (box-shadow, not border) so a row that's both in écart and signalée shows both indicators without one overwriting the other. - Native node add/edit form gets the field for free via core's entity_reference_autocomplete_tags widget. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
596 lines
17 KiB
CSS
596 lines
17 KiB
CSS
/* Self-contained colors (not relying on Gin CSS custom properties, which
|
|
don't reliably resolve on this route) + explicit dark-mode overrides,
|
|
since Gin's dark mode (html.gin--dark-mode) is enabled by default here. */
|
|
#figli-home-app {
|
|
--figli-bg: #ffffff;
|
|
--figli-bg-alt: #f5f6f8;
|
|
--figli-text: #1a1a1a;
|
|
--figli-text-light: #6b7280;
|
|
--figli-border: #dcdee2;
|
|
--figli-error: #c9312b;
|
|
--figli-positive: #1a7f37;
|
|
--figli-warning: #b8860b;
|
|
--figli-col-hover: rgba(15, 23, 42, 0.05);
|
|
|
|
font-family: Inter, -apple-system, sans-serif;
|
|
margin: 1rem 0;
|
|
color: var(--figli-text);
|
|
}
|
|
|
|
html.gin--dark-mode #figli-home-app {
|
|
--figli-bg: #232426;
|
|
--figli-bg-alt: #2c2d30;
|
|
--figli-text: #e8e9ea;
|
|
--figli-text-light: #a1a5ab;
|
|
--figli-border: #3d3e42;
|
|
--figli-error: #ff6b6b;
|
|
--figli-positive: #4ade80;
|
|
--figli-warning: #f0b429;
|
|
--figli-col-hover: rgba(255, 255, 255, 0.07);
|
|
}
|
|
|
|
#figli-home-app .figli-toolbar {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
gap: 0.75rem 1.25rem;
|
|
margin-bottom: 1rem;
|
|
padding: 0.75rem 1rem;
|
|
background: var(--figli-bg-alt);
|
|
border: 1px solid var(--figli-border);
|
|
border-radius: 6px;
|
|
color: var(--figli-text);
|
|
}
|
|
|
|
#figli-home-app .figli-toolbar label {
|
|
display: flex;
|
|
flex-direction: column;
|
|
font-size: 0.75rem;
|
|
font-weight: 600;
|
|
color: var(--figli-text-light);
|
|
gap: 0.15rem;
|
|
}
|
|
|
|
#figli-home-app .figli-toolbar select {
|
|
font-size: 0.85rem;
|
|
padding: 0.2rem 0.4rem;
|
|
background: var(--figli-bg);
|
|
color: var(--figli-text);
|
|
border: 1px solid var(--figli-border);
|
|
border-radius: 4px;
|
|
}
|
|
|
|
/* Each filter control + its "✕ effacer" button, side by side. */
|
|
#figli-home-app .figli-filter-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.25rem;
|
|
}
|
|
#figli-home-app .figli-filter-clear {
|
|
background: transparent;
|
|
border: none;
|
|
color: var(--figli-text-light);
|
|
cursor: pointer;
|
|
font-size: 0.8rem;
|
|
line-height: 1;
|
|
padding: 0.15rem;
|
|
}
|
|
#figli-home-app .figli-filter-clear:hover {
|
|
color: var(--figli-error);
|
|
}
|
|
|
|
/* Compte/Type filters: a native <select multiple> can't collapse to a
|
|
single-line trigger and expand as an overlay -- it's either a
|
|
single-line dropdown, or an always-open multi-row list box. Custom
|
|
dropdown instead: a trigger showing the current selection, and a
|
|
checkbox list overlay shown on hover (pure CSS, no open/close state
|
|
needed) -- checkboxes rather than ctrl/shift+click, but same result
|
|
(several comptes/types at once) with an even more discoverable
|
|
interaction (no keyboard modifier to know about).*/
|
|
#figli-home-app .figli-multiselect {
|
|
position: relative;
|
|
}
|
|
#figli-home-app .figli-multiselect-trigger {
|
|
font-size: 0.85rem;
|
|
padding: 0.2rem 0.4rem;
|
|
background: var(--figli-bg);
|
|
color: var(--figli-text);
|
|
border: 1px solid var(--figli-border);
|
|
border-radius: 4px;
|
|
width: 9rem;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
cursor: default;
|
|
}
|
|
#figli-home-app .figli-multiselect-arrow {
|
|
float: right;
|
|
color: var(--figli-text-light);
|
|
}
|
|
#figli-home-app .figli-multiselect-panel {
|
|
display: none;
|
|
position: absolute;
|
|
top: 100%;
|
|
left: 0;
|
|
z-index: 20;
|
|
min-width: 100%;
|
|
white-space: nowrap;
|
|
background: var(--figli-bg);
|
|
color: var(--figli-text);
|
|
border: 1px solid var(--figli-border);
|
|
border-radius: 4px;
|
|
padding: 0.3rem 0.5rem;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
}
|
|
#figli-home-app .figli-multiselect:hover .figli-multiselect-panel {
|
|
display: block;
|
|
}
|
|
#figli-home-app .figli-multiselect-option {
|
|
display: flex !important;
|
|
flex-direction: row !important;
|
|
align-items: center;
|
|
gap: 0.35rem;
|
|
font-size: 0.8rem;
|
|
font-weight: 400;
|
|
padding: 0.15rem 0;
|
|
}
|
|
|
|
#figli-home-app .figli-client-input {
|
|
font-size: 0.85rem;
|
|
padding: 0.2rem 0.4rem;
|
|
background: var(--figli-bg);
|
|
color: var(--figli-text);
|
|
border: 1px solid var(--figli-border);
|
|
border-radius: 4px;
|
|
width: 12rem;
|
|
}
|
|
|
|
#figli-home-app .figli-checkbox {
|
|
flex-direction: row !important;
|
|
align-items: center;
|
|
gap: 0.35rem !important;
|
|
}
|
|
|
|
#figli-home-app .figli-count {
|
|
margin-left: auto;
|
|
font-size: 0.8rem;
|
|
color: var(--figli-text-light);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
#figli-home-app .figli-table-wrap {
|
|
overflow-x: auto;
|
|
border: 1px solid var(--figli-border);
|
|
border-radius: 6px;
|
|
max-height: 75vh;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
#figli-home-app table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
background: var(--figli-bg);
|
|
color: var(--figli-text);
|
|
font-size: 0.8rem;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
#figli-home-app th,
|
|
#figli-home-app td {
|
|
text-align: left;
|
|
padding: 0.3rem 0.6rem;
|
|
border-bottom: 1px solid var(--figli-border);
|
|
color: var(--figli-text);
|
|
}
|
|
|
|
#figli-home-app thead th {
|
|
position: sticky;
|
|
top: 0;
|
|
background: var(--figli-bg-alt);
|
|
color: var(--figli-text);
|
|
font-weight: 600;
|
|
font-size: 0.72rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.02em;
|
|
z-index: 2;
|
|
}
|
|
|
|
#figli-home-app td.amount,
|
|
#figli-home-app th.amount {
|
|
text-align: right;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
#figli-home-app td.figli-libelle {
|
|
white-space: normal;
|
|
min-width: 220px;
|
|
max-width: 340px;
|
|
}
|
|
|
|
#figli-home-app td.figli-flag-cell {
|
|
white-space: normal;
|
|
min-width: 120px;
|
|
max-width: 220px;
|
|
}
|
|
|
|
#figli-home-app tr.figli-group-row td {
|
|
background: var(--figli-bg-alt);
|
|
color: var(--figli-text);
|
|
font-weight: 700;
|
|
position: sticky;
|
|
top: 2rem;
|
|
z-index: 1;
|
|
}
|
|
|
|
#figli-home-app tfoot {
|
|
position: sticky;
|
|
bottom: 0;
|
|
/* Higher than thead (2) and the sticky group-row headers (1) -- without
|
|
this, tfoot had no explicit z-index (auto), so a group-row header
|
|
scrolling underneath it painted on top instead of behind. */
|
|
z-index: 3;
|
|
}
|
|
|
|
#figli-home-app tr.figli-totals-row td {
|
|
background: var(--figli-bg-alt);
|
|
color: var(--figli-text);
|
|
font-weight: 700;
|
|
border-top: 2px solid var(--figli-border);
|
|
border-bottom: none;
|
|
}
|
|
|
|
#figli-home-app td.figli-solde-crediteur {
|
|
color: var(--figli-positive);
|
|
}
|
|
#figli-home-app td.figli-solde-debiteur {
|
|
color: var(--figli-error);
|
|
}
|
|
/* Footer totals row sets its own `color` (higher specificity: tr.class +
|
|
td beats a lone td.class) which otherwise overrides the solde colors
|
|
above -- repeat them scoped to the row so red/green still wins there. */
|
|
#figli-home-app tr.figli-totals-row td.figli-solde-crediteur {
|
|
color: var(--figli-positive);
|
|
}
|
|
#figli-home-app tr.figli-totals-row td.figli-solde-debiteur {
|
|
color: var(--figli-error);
|
|
}
|
|
|
|
/* Single colored + bold anchor per row (Montant HT only) so entrées vs
|
|
sorties are scannable at a glance -- everything else in the row (TTC,
|
|
the 8 compte columns) stays neutral, not a red/green garland. */
|
|
#figli-home-app td.figli-montant-positif {
|
|
color: var(--figli-positive);
|
|
font-weight: 700;
|
|
}
|
|
#figli-home-app td.figli-montant-negatif {
|
|
color: var(--figli-error);
|
|
font-weight: 700;
|
|
}
|
|
|
|
/* Error rows: a thin red outline around the row, not a background fill --
|
|
easier to read, doesn't fight with dark mode. */
|
|
#figli-home-app tr.figli-error-row td {
|
|
border-top: 1px solid var(--figli-error);
|
|
border-bottom: 1px solid var(--figli-error);
|
|
}
|
|
#figli-home-app tr.figli-error-row td:first-child {
|
|
border-left: 1px solid var(--figli-error);
|
|
}
|
|
#figli-home-app tr.figli-error-row td:last-child {
|
|
border-right: 1px solid var(--figli-error);
|
|
}
|
|
|
|
#figli-home-app td.figli-ecart {
|
|
color: var(--figli-error);
|
|
font-weight: 700;
|
|
}
|
|
|
|
/* Signalé rows: a thick amber accent bar on the left edge, deliberately
|
|
a different technique from the error row's thin all-around border
|
|
above (box-shadow, not border) -- so a row that's both in écart *and*
|
|
signalée shows both indicators at once instead of one overwriting the
|
|
other's border-left. */
|
|
#figli-home-app tr.figli-flag-row td:first-child {
|
|
box-shadow: inset 4px 0 0 var(--figli-warning);
|
|
}
|
|
|
|
#figli-home-app .figli-flag-badge {
|
|
display: inline-block;
|
|
padding: 0.05rem 0.4rem;
|
|
margin: 0 0.2rem 0.15rem 0;
|
|
border-radius: 8px;
|
|
font-size: 0.68rem;
|
|
font-weight: 600;
|
|
background: color-mix(in srgb, var(--figli-warning) 15%, transparent);
|
|
color: var(--figli-warning);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* Column highlight to pair with the row hover, forming a crosshair over
|
|
the hovered cell. box-shadow (not background) so it layers on top of
|
|
whatever the cell already has -- sticky header/footer backgrounds,
|
|
error-row outlines -- instead of overwriting them. */
|
|
#figli-home-app td.figli-col-hover,
|
|
#figli-home-app th.figli-col-hover {
|
|
box-shadow: inset 0 0 0 9999px var(--figli-col-hover);
|
|
}
|
|
|
|
#figli-home-app .figli-badge {
|
|
display: inline-block;
|
|
padding: 0.1rem 0.45rem;
|
|
border-radius: 10px;
|
|
font-size: 0.68rem;
|
|
font-weight: 600;
|
|
white-space: nowrap;
|
|
}
|
|
#figli-home-app .type-entree { background: #1a7f371a; color: var(--figli-positive); }
|
|
#figli-home-app .type-charge { background: #6b72801a; color: var(--figli-text-light); }
|
|
#figli-home-app .type-versement { background: #d97a0a1a; color: #d97a0a; }
|
|
#figli-home-app .type-achat { background: #1d4ed81a; color: #3b6fe0; }
|
|
#figli-home-app .type-hebergement { background: #0e91821a; color: #0e9182; }
|
|
#figli-home-app .type-ouverture { background: #7c3aed1a; color: #9061f0; }
|
|
#figli-home-app .type-sous_traitant { background: #c9312b1a; color: #c9312b; }
|
|
#figli-home-app .type-salaire_stage { background: #0891b21a; color: #0891b2; }
|
|
#figli-home-app .type-charges_local_pro { background: #65a30d1a; color: #65a30d; }
|
|
#figli-home-app .type-autre { background: #6b72801a; color: var(--figli-text-light); }
|
|
|
|
/* Click-to-edit type badge -- swaps for a native <select> in place
|
|
(figli-type-select below), no modal needed for this one field. */
|
|
#figli-home-app .figli-badge-editable {
|
|
cursor: pointer;
|
|
border: 1px solid transparent;
|
|
}
|
|
#figli-home-app .figli-badge-editable:hover {
|
|
border-color: currentColor;
|
|
}
|
|
#figli-home-app .figli-type-select {
|
|
font-size: 0.72rem;
|
|
padding: 0.05rem 0.2rem;
|
|
background: var(--figli-bg);
|
|
color: var(--figli-text);
|
|
border: 1px solid var(--figli-border);
|
|
border-radius: 4px;
|
|
}
|
|
|
|
/* Client/Facture/Libellé: click-to-edit like the type badge above, but
|
|
plain text rather than a pill -- a dotted underline is enough of an
|
|
affordance without implying a fixed set of choices the way the type
|
|
badge's pill shape does. */
|
|
/* display: block + min-height (not just the default inline text-only
|
|
box) so the whole cell is a forgiving click target even when empty --
|
|
the "—" placeholder alone is a tiny, easy-to-miss target otherwise. */
|
|
#figli-home-app .figli-editable-cell {
|
|
display: block;
|
|
min-height: 1.2em;
|
|
cursor: pointer;
|
|
border-bottom: 1px dotted transparent;
|
|
}
|
|
#figli-home-app .figli-editable-cell:hover {
|
|
border-bottom-color: var(--figli-text-light);
|
|
}
|
|
#figli-home-app .figli-inline-input {
|
|
font-size: 0.8rem;
|
|
padding: 0.1rem 0.3rem;
|
|
background: var(--figli-bg);
|
|
color: var(--figli-text);
|
|
border: 1px solid var(--figli-border);
|
|
border-radius: 4px;
|
|
width: 100%;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
#figli-home-app .figli-note {
|
|
color: var(--figli-text-light);
|
|
font-weight: 400;
|
|
font-size: 0.75rem;
|
|
}
|
|
|
|
/* One column per icon (link, edit) rather than one shared cell -- with a
|
|
shared cell, text-align: center puts a lone button (rows with no link
|
|
icon) in a different spot than a row with both, so the edit pencil
|
|
never lined up from row to row. Separate columns line up by
|
|
construction, and get a tight, icon-sized width instead of the
|
|
table's default 0.6rem horizontal cell padding. */
|
|
#figli-home-app td.actions-col,
|
|
#figli-home-app th.actions-col {
|
|
width: 1%;
|
|
padding: 0.2rem 0.1rem;
|
|
text-align: center;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* Buttons stay inline (not flex) inside the <td> -- display: flex on a
|
|
table cell breaks the table's own column-width calculation (observed:
|
|
a ~55px gap opening up between this column and the next, and the
|
|
buttons rendering outside their cell's actual boundary). */
|
|
#figli-home-app .figli-edit-btn,
|
|
#figli-home-app .figli-link-btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 1.4rem;
|
|
height: 1.4rem;
|
|
padding: 0;
|
|
background: transparent;
|
|
border: 1px solid transparent;
|
|
border-radius: 4px;
|
|
color: var(--figli-text-light);
|
|
cursor: pointer;
|
|
vertical-align: middle;
|
|
}
|
|
|
|
#figli-home-app .figli-edit-btn:hover,
|
|
#figli-home-app .figli-link-btn:hover {
|
|
background: var(--figli-bg-alt);
|
|
border-color: var(--figli-border);
|
|
color: var(--figli-text);
|
|
}
|
|
|
|
#figli-home-app .figli-link-btn.is-linked {
|
|
color: #3b6fe0;
|
|
}
|
|
|
|
/* Reconciliation badge on entrée rows: répartition (money owed) vs the
|
|
combined répartition of every linked sortie (money actually paid out). */
|
|
#figli-home-app .figli-recon-badge {
|
|
display: inline-block;
|
|
margin-left: 0.4rem;
|
|
padding: 0.05rem 0.4rem;
|
|
border-radius: 10px;
|
|
font-size: 0.68rem;
|
|
font-weight: 600;
|
|
white-space: nowrap;
|
|
background: #1a7f371a;
|
|
color: var(--figli-positive);
|
|
}
|
|
/* Not every badge using this class does something on click (the
|
|
ouverture/clôture écart badge is purely informational) -- only show
|
|
the pointer cursor where a click actually goes somewhere. */
|
|
#figli-home-app .figli-recon-badge.is-clickable {
|
|
cursor: pointer;
|
|
}
|
|
#figli-home-app .figli-recon-badge.is-reste {
|
|
background: #d97a0a1a;
|
|
color: #d97a0a;
|
|
}
|
|
#figli-home-app .figli-recon-badge.is-anomalie {
|
|
background: #c9312b1a;
|
|
color: var(--figli-error);
|
|
}
|
|
|
|
#figli-home-app .figli-clear-drilldown {
|
|
font-size: 0.8rem;
|
|
padding: 0.2rem 0.6rem;
|
|
}
|
|
|
|
/* Entrée + sorties liées drill-down: a modal overlay rather than
|
|
replacing the main table's rows in place, so opening/closing it never
|
|
disturbs the main table's scroll position. */
|
|
#figli-home-app .figli-modal-backdrop {
|
|
position: fixed;
|
|
inset: 0;
|
|
background: rgba(0, 0, 0, 0.5);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
z-index: 100;
|
|
}
|
|
#figli-home-app .figli-modal {
|
|
background: var(--figli-bg);
|
|
color: var(--figli-text);
|
|
border: 1px solid var(--figli-border);
|
|
border-radius: 8px;
|
|
width: 96vw;
|
|
max-height: 85vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
|
|
}
|
|
#figli-home-app .figli-modal-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 0.75rem 1rem;
|
|
border-bottom: 1px solid var(--figli-border);
|
|
}
|
|
#figli-home-app .figli-modal-header h3 {
|
|
margin: 0;
|
|
font-size: 0.95rem;
|
|
font-weight: 700;
|
|
}
|
|
#figli-home-app .figli-modal-close {
|
|
background: transparent;
|
|
border: 1px solid transparent;
|
|
border-radius: 4px;
|
|
color: var(--figli-text-light);
|
|
cursor: pointer;
|
|
font-size: 0.9rem;
|
|
padding: 0.15rem 0.45rem;
|
|
}
|
|
#figli-home-app .figli-modal-close:hover {
|
|
background: var(--figli-bg-alt);
|
|
border-color: var(--figli-border);
|
|
color: var(--figli-text);
|
|
}
|
|
#figli-home-app .figli-modal-body {
|
|
overflow-y: auto;
|
|
overflow-x: hidden;
|
|
padding: 0;
|
|
}
|
|
#figli-home-app .figli-modal-body table {
|
|
font-size: 0.8rem;
|
|
table-layout: fixed;
|
|
width: 100%;
|
|
/* Only a handful of rows in this view -- wrap instead of the main
|
|
table's nowrap, so 14 columns fit the modal width without ever
|
|
needing horizontal scroll. */
|
|
white-space: normal;
|
|
}
|
|
#figli-home-app .figli-modal-body thead th {
|
|
position: sticky;
|
|
top: 0;
|
|
}
|
|
/* table-layout: fixed otherwise spreads all 16 columns evenly, squeezing
|
|
the libellé text into the same width as an empty compte column --
|
|
give the columns that actually carry content (client/type/libellé)
|
|
proportionally more room, comptes/montants less. */
|
|
#figli-home-app .figli-modal-body th:nth-child(1),
|
|
#figli-home-app .figli-modal-body td:nth-child(1) {
|
|
width: 2rem;
|
|
}
|
|
#figli-home-app .figli-modal-body th:nth-child(2),
|
|
#figli-home-app .figli-modal-body td:nth-child(2) {
|
|
width: 4.5rem;
|
|
}
|
|
#figli-home-app .figli-modal-body th:nth-child(3),
|
|
#figli-home-app .figli-modal-body td:nth-child(3) {
|
|
width: 9%;
|
|
}
|
|
#figli-home-app .figli-modal-body th:nth-child(4),
|
|
#figli-home-app .figli-modal-body td:nth-child(4) {
|
|
width: 8%;
|
|
}
|
|
#figli-home-app .figli-modal-body th:nth-child(5),
|
|
#figli-home-app .figli-modal-body td.figli-libelle {
|
|
width: 24%;
|
|
min-width: 0;
|
|
max-width: none;
|
|
}
|
|
#figli-home-app .figli-modal-body .amount.compte-col {
|
|
width: 4.5%;
|
|
}
|
|
#figli-home-app .figli-modal-body .amount:not(.compte-col) {
|
|
width: 7%;
|
|
}
|
|
|
|
/* Sliding-window edge markers (IntersectionObserver targets) -- kept
|
|
short so they don't add visible dead space when idle, tall enough
|
|
(min-height) to reliably intersect the observer's root margin. */
|
|
#figli-home-app tr.figli-sentinel-row td {
|
|
padding: 0.3rem 0.6rem;
|
|
min-height: 1.5rem;
|
|
text-align: center;
|
|
color: var(--figli-text-light);
|
|
font-size: 0.75rem;
|
|
font-style: italic;
|
|
}
|
|
|
|
#figli-home-app .figli-error {
|
|
background: #fde8e8;
|
|
border: 1px solid #f4a3a3;
|
|
color: #7a1a1a;
|
|
padding: 0.75rem 1rem;
|
|
border-radius: 6px;
|
|
}
|
|
|
|
/* Dismissible, doesn't replace the table like the top-level fetch error
|
|
does -- a failed inline type change is a small hiccup, not a reason to
|
|
hide everything that's already loaded. */
|
|
#figli-home-app .figli-inline-error {
|
|
margin-bottom: 1rem;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 0.75rem;
|
|
}
|