Chaque transaction du relevé devient une ligne « à trier » : type autre, répartition vide (liseré rouge existant via field_ecart), tag field_flag « IMP AAMMJJ » (liseré ambre + filtre existants), client rapproché par mots (ClientMatcher : séquence contiguë, sinon mot significatif unique -- jamais en sous-chaîne, jamais auto-créé). - src/Import/ : CsvReleveParser (ISO-8859-1 confirmé, en-tête strict, fgetcsv avec escape '' explicite -- dépréciation PHP 8.4, rejets propres avec n° de ligne), ReleveImportBatch (Batch API par lots de 25, dédoublonnage COMPTÉ par empreinte field_import_fitid -- max(0, k−m) importe les vrais doublons légitimes et dédoublonne à travers des fichiers qui se chevauchent --, totaux de contrôle au centime sur la page de résultat, résumé en tempstore privé). - ReleveUploadForm : upload private://releves (fichier conservé + usage, hors de portée du cron), parse en validateForm(), batch, redirection vers la page de résultat. - field_montant_releve : référence bancaire immuable, écrite une fois à l'import et jamais par presave ; affichée en TEXTE sous Montant TTC (widget remplacé par un #type item -- un item ne soumet rien et extractFormValues() saute le champ sans valeur soumise, la valeur survit donc à chaque save) ; masquée sur les lignes sans montant. - SkipValidationContext : contournement du contrôle de répartition requête-scopé (ferme le trou de concurrence de l'ancien state global, AUDIT-2026-09-09 §2.2) -- presave honore le service (state gardé pour compat), updateType/updateField basculent dessus. - Permissions (AUDIT §2.2 priorité 1) : access figli ledger sur toutes les routes du module + autocomplete (RouteSubscriber), import réservé Éditeur/Admin, access content retiré du rôle Authenticated (config/sync re-exportée pour les 4 rôles). - Gin : hook_gin_ignore_sticky_form_actions() -- sans ça, le bouton Importer partait dans la barre sticky du chrome masqué. - install : 8011 champs, 8012 index sur les empreintes, 8013/8014 montant_releve sur le formulaire sous le TTC (poids renumérotés). - /lignes : boutons + Ajouter / Importer / Historique dans le footer sticky (compacts), footer colspan dès la première colonne, badges de signalement qui reviennent à la ligne au lieu de déborder.
769 lines
24 KiB
CSS
769 lines
24 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;
|
|
/* Bottom cut to a third (top left alone) -- with the outer page
|
|
scroll already eliminated (see hide-admin-chrome.css's main.page-
|
|
content override), the full 1rem below the table was just dead
|
|
space past the last visible row. */
|
|
margin: 1rem 0 0.33rem;
|
|
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 {
|
|
/* Fixed columns below are sized to always fit -- hidden (not auto) so
|
|
there's never a horizontal scrollbar to fight the vertical one for
|
|
layout space. With both set to auto, the browser has to guess
|
|
whether a vertical scrollbar will appear before it can lay out the
|
|
table's width:100%, and getting that guess wrong understates the
|
|
available width by one scrollbar's worth, which was enough to tip
|
|
this from "just barely fits" into "needs to scroll horizontally
|
|
too" -- and any actual residual overflow (a column pushed a few px
|
|
over by content this doesn't attempt to break) is silently clipped
|
|
here instead of surfacing a scrollbar for it. */
|
|
overflow-x: hidden;
|
|
border: 1px solid var(--figli-border);
|
|
border-radius: 6px;
|
|
/* Not a flat 75vh -- title/nav + toolbar (with its filter row) above
|
|
this add up to a roughly fixed ~16rem regardless of viewport
|
|
height, so a percentage-of-viewport max-height only fits by
|
|
coincidence at one particular window size and silently reintroduces
|
|
the outer page scroll (on top of this element's own) at any other,
|
|
as toolbar filters were added over time and pushed that fixed part
|
|
past what 75vh left room for. Subtracting the fixed part directly
|
|
keeps this sized to exactly the remaining space instead. */
|
|
max-height: calc(100vh - 16rem);
|
|
overflow-y: auto;
|
|
}
|
|
|
|
/* table-layout: fixed -- auto layout was recomputing every column's
|
|
width from whatever happened to be currently loaded, so the table
|
|
visibly reflowed (and drifted wider than the viewport, forcing
|
|
horizontal scroll) every time loadOlder()/loadNewer() brought in rows
|
|
with different content. Fixed layout locks each column to the widths
|
|
set below regardless of content, so the table's total width never
|
|
changes -- no more horizontal scroll, and loadOlder()'s scroll
|
|
compensation (which assumes the height delta after prepending is
|
|
*only* the new rows' own height) is now accurate again, since existing
|
|
rows no longer reflow when new ones are added. */
|
|
#figli-home-app table {
|
|
width: 100%;
|
|
table-layout: fixed;
|
|
border-collapse: collapse;
|
|
background: var(--figli-bg);
|
|
color: var(--figli-text);
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
#figli-home-app th,
|
|
#figli-home-app td {
|
|
/* border-box -- otherwise each column's percentage width (fixed
|
|
layout, above) sets only the content box, and this cell's own
|
|
padding/border get added on top of it, so 18 columns' worth of
|
|
padding quietly pushes the table wider than 100% again. */
|
|
box-sizing: border-box;
|
|
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 th:nth-child(6),
|
|
#figli-home-app td.figli-libelle {
|
|
width: 10%;
|
|
white-space: normal;
|
|
}
|
|
|
|
#figli-home-app th:nth-child(7),
|
|
#figli-home-app td.figli-flag-cell {
|
|
width: 6%;
|
|
white-space: normal;
|
|
}
|
|
|
|
#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;
|
|
}
|
|
|
|
/* "+ Ajouter une ligne" + "Importer un relevé" live in the sticky footer
|
|
now (they used to lead the toolbar and crowd its filter row). Compact
|
|
overrides for Gin's .button, which is sized for full admin forms --
|
|
way too big inside a dense totals row. Size-only overrides (no
|
|
colors): Gin's own light/dark button palettes keep applying. */
|
|
#figli-home-app tr.figli-totals-row .button {
|
|
display: inline-block;
|
|
margin: 0 0.4rem 0 0;
|
|
padding: 0.15rem 0.55rem;
|
|
font-size: 0.72rem;
|
|
line-height: 1.4;
|
|
vertical-align: middle;
|
|
border-radius: 4px;
|
|
box-shadow: 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;
|
|
}
|
|
|
|
/* Écart is called out by the Écart column itself (bold, red or green
|
|
depending on sign -- see below) -- no separate row-level treatment
|
|
needed. Répartition > Montant HT (positive écart) means the accounts
|
|
were credited more than what's actually owed for this line, the same
|
|
"more money than expected" sense .figli-montant-positif already
|
|
marks green elsewhere; the reverse (negative) stays the usual
|
|
red/error color. */
|
|
#figli-home-app td.figli-ecart {
|
|
color: var(--figli-error);
|
|
font-weight: 700;
|
|
}
|
|
#figli-home-app td.figli-ecart-positif {
|
|
color: var(--figli-positive);
|
|
font-weight: 700;
|
|
}
|
|
|
|
/* Signalé rows: a thick amber accent bar on the left edge. */
|
|
#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);
|
|
/* The Signalement column is narrow (6%) -- a badge must wrap inside
|
|
the cell instead of overflowing into the neighboring column. The
|
|
cell itself already allows wrapping (.figli-flag-cell); this makes
|
|
the badge wrap too, including single long tokens (anywhere) and
|
|
within its own padding box (max-width + border-box). */
|
|
box-sizing: border-box;
|
|
max-width: 100%;
|
|
white-space: normal;
|
|
overflow-wrap: anywhere;
|
|
}
|
|
|
|
/* 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: 3%;
|
|
padding: 0.2rem 0.1rem;
|
|
text-align: center;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* Column widths for the fixed layout above -- sized so the widest
|
|
realistic content (longest client/type label, an 8-figure amount)
|
|
fits without pushing the table past 100%, while the two free-text
|
|
columns (Client, Libellé/Détail) get most of the remaining room.
|
|
Every column is a percentage (including actions-col/Date above,
|
|
deliberately not rem) -- mixing units meant the rem columns' pixel
|
|
width was added *on top of* the percentage budget instead of coming
|
|
out of it, silently pushing the table a few pixels past 100% and
|
|
reintroducing the horizontal scrollbar this is meant to avoid.
|
|
Percentages intentionally sum to a little under 100%: table-layout:
|
|
fixed treats them as relative weights, not a hard budget, so slightly
|
|
under leaves headroom rather than risking every column getting
|
|
scaled down to fit. */
|
|
#figli-home-app th:nth-child(2),
|
|
#figli-home-app td:nth-child(2) {
|
|
width: 5%;
|
|
white-space: nowrap;
|
|
}
|
|
#figli-home-app th:nth-child(3),
|
|
#figli-home-app td:nth-child(3) {
|
|
width: 10%;
|
|
}
|
|
#figli-home-app th:nth-child(4),
|
|
#figli-home-app td:nth-child(4) {
|
|
width: 7%;
|
|
}
|
|
#figli-home-app th:nth-child(5),
|
|
#figli-home-app td:nth-child(5) {
|
|
width: 5%;
|
|
white-space: nowrap;
|
|
}
|
|
#figli-home-app .amount:not(.compte-col) {
|
|
width: 6.2%;
|
|
white-space: nowrap;
|
|
}
|
|
/* HT/TTC narrower than the general .amount rule above (still used by
|
|
Écart) -- shorter header text (HT/TTC vs. the old Montant HT/Montant
|
|
TTC) and TVA splitting the row between them means neither needs as
|
|
much room. TVA itself narrower still: "12,64 %" is the longest
|
|
realistic value (see figli_compta_ledger_update_8005()'s docblock on
|
|
backfilled non-round rates), well short of an 8-figure amount.
|
|
`.amount` doubled up in the selector (not just .figli-ht-col alone):
|
|
:not(.compte-col) in the rule above counts toward specificity same as
|
|
a real class, so a single-class selector here would lose to it. */
|
|
#figli-home-app .amount.figli-ht-col,
|
|
#figli-home-app .amount.figli-ttc-col {
|
|
width: 5%;
|
|
}
|
|
/* Cotisation diffuseur URSSAF (1,1%) -- blank for every non-"Entrée
|
|
client" row (see figli_compta_ledger_node_presave()), same width
|
|
budget as HT/TTC since it's a real € amount, not a short rate like
|
|
TVA. */
|
|
#figli-home-app .amount.figli-cotisation-col {
|
|
width: 5%;
|
|
}
|
|
#figli-home-app .amount.figli-tva-col {
|
|
width: 3.5%;
|
|
}
|
|
/* Non-official TVA rate marker -- a thick red underline on the cell
|
|
itself rather than a small badge/dot (tried first, not visible
|
|
enough): the column is only ~3.5% wide, barely enough for
|
|
"12,64 %" itself, so a border under the whole cell reads clearly at
|
|
a glance without needing extra horizontal room a text/icon badge
|
|
would need. Tooltip (title attribute in the twig template) carries
|
|
the actual explanation. */
|
|
#figli-home-app td.figli-tva-nonofficial {
|
|
border-bottom: 3px solid var(--figli-error);
|
|
}
|
|
#figli-home-app .amount.compte-col {
|
|
width: 4.3%;
|
|
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 {
|
|
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 {
|
|
background: var(--figli-bg-alt);
|
|
border-color: var(--figli-border);
|
|
color: var(--figli-text);
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
/* Still green, not orange -- it's money owed, not a warning, and
|
|
green-but-darker reads as "related to, but not yet, Lié" (the default
|
|
badge green above) rather than as an unrelated anomaly color. */
|
|
#figli-home-app .figli-recon-badge.is-reste {
|
|
background: #1665341a;
|
|
color: #166534;
|
|
}
|
|
#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);
|
|
}
|
|
/* Signalement modal -- much smaller than the entrées/sorties drill-down
|
|
above (a short list of tags, not a table), so it gets its own narrow
|
|
width instead of the 96vw default. */
|
|
#figli-home-app .figli-flag-modal {
|
|
width: 24rem;
|
|
max-width: 90vw;
|
|
}
|
|
#figli-home-app .figli-flag-modal-body {
|
|
padding: 0.75rem 1rem 1rem;
|
|
}
|
|
#figli-home-app .figli-flag-modal-row {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 0.5rem;
|
|
padding: 0.35rem 0;
|
|
border-bottom: 1px solid var(--figli-border);
|
|
}
|
|
#figli-home-app .figli-flag-remove {
|
|
background: transparent;
|
|
border: 1px solid transparent;
|
|
border-radius: 4px;
|
|
color: var(--figli-text-light);
|
|
cursor: pointer;
|
|
font-size: 0.8rem;
|
|
padding: 0.1rem 0.4rem;
|
|
flex: none;
|
|
}
|
|
#figli-home-app .figli-flag-remove:hover {
|
|
background: var(--figli-bg-alt);
|
|
border-color: var(--figli-border);
|
|
color: var(--figli-error);
|
|
}
|
|
#figli-home-app .figli-flag-modal-add {
|
|
display: flex;
|
|
gap: 0.5rem;
|
|
margin-top: 0.75rem;
|
|
}
|
|
#figli-home-app .figli-flag-modal-add input {
|
|
flex: 1;
|
|
font-size: 0.85rem;
|
|
padding: 0.3rem 0.5rem;
|
|
border: 1px solid var(--figli-border);
|
|
border-radius: 4px;
|
|
background: var(--figli-bg);
|
|
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;
|
|
}
|