Gestion des Thèmes
This commit is contained in:
1 parent
bab7a79b63
commit
be3c93897b
24 files changed
+1041
-141
No files matched your search
@@ -10,10 +10,13 @@ import { useMemo, useState, useRef } from 'react';
|
||||
* montant par frais).
|
||||
*/
|
||||
|
||||
const COLOR = '#e8547a';
|
||||
const BG = '#070c15';
|
||||
const GRID = 'rgba(255,255,255,0.055)';
|
||||
const LABEL = '#4a5568';
|
||||
/* Couleurs alignées sur le thème actif (Admin > Général, cf.
|
||||
* project_theme_interface.md) plutôt que fixées en dur — même mécanisme
|
||||
* que InvChart.jsx / InteretsDonutChart.jsx. */
|
||||
const COLOR = 'var(--primary)';
|
||||
const BG = 'var(--surface)';
|
||||
const GRID = 'var(--border)';
|
||||
const LABEL = 'var(--text-muted)';
|
||||
|
||||
const RANGES = ['1J', '7J', '1M', '3M', 'YTD', '1A', 'TOUT'];
|
||||
const MOIS_COURT = ['jan.','fév.','mar.','avr.','mai','juin','juil.','août','sep.','oct.','nov.','déc.'];
|
||||
@@ -247,7 +250,7 @@ export default function FraisCumulChart({ rows }) {
|
||||
{/* Ligne zéro */}
|
||||
{yZero && yZero > PAD.top && yZero < PAD.top + plotH && (
|
||||
<line x1={PAD.left} y1={yZero} x2={W - PAD.right} y2={yZero}
|
||||
stroke="rgba(255,255,255,0.18)" strokeWidth="1" />
|
||||
stroke={GRID} strokeWidth="1" />
|
||||
)}
|
||||
|
||||
{/* Remplissage dégradé */}
|
||||
@@ -273,7 +276,7 @@ export default function FraisCumulChart({ rows }) {
|
||||
{hover && (
|
||||
<g>
|
||||
<line x1={hover.x} y1={PAD.top} x2={hover.x} y2={PAD.top + plotH}
|
||||
stroke="rgba(255,255,255,0.12)" strokeWidth="1" strokeDasharray="3 4" />
|
||||
stroke={GRID} strokeWidth="1" strokeDasharray="3 4" />
|
||||
<circle cx={hover.x} cy={hover.y} r="4.5"
|
||||
fill={COLOR} stroke={BG} strokeWidth="2" />
|
||||
</g>
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { useMemo, useState, useRef } from 'react';
|
||||
|
||||
/* ── Constantes ─────────────────────────────────────────────── */
|
||||
const COLOR = '#4fa8e8';
|
||||
const BG = '#070c15';
|
||||
const GRID = 'rgba(255,255,255,0.055)';
|
||||
const LABEL = '#4a5568';
|
||||
/* Couleurs alignées sur le thème actif (Admin > Général, cf.
|
||||
* project_theme_interface.md) plutôt que fixées en dur — même mécanisme
|
||||
* que InvChart.jsx / InteretsDonutChart.jsx. */
|
||||
const COLOR = 'var(--primary)';
|
||||
const BG = 'var(--surface)';
|
||||
const GRID = 'var(--border)';
|
||||
const LABEL = 'var(--text-muted)';
|
||||
|
||||
const RANGES = ['1J', '7J', '1M', '3M', 'YTD', '1A', 'TOUT'];
|
||||
const MOIS_COURT = ['jan.','fév.','mar.','avr.','mai','juin','juil.','août','sep.','oct.','nov.','déc.'];
|
||||
@@ -237,7 +240,7 @@ export default function InteretsChart({ rows, netMode }) {
|
||||
{/* Ligne zéro */}
|
||||
{yZero && yZero > PAD.top && yZero < PAD.top + plotH && (
|
||||
<line x1={PAD.left} y1={yZero} x2={W - PAD.right} y2={yZero}
|
||||
stroke="rgba(255,255,255,0.18)" strokeWidth="1" />
|
||||
stroke={GRID} strokeWidth="1" />
|
||||
)}
|
||||
|
||||
{/* Remplissage dégradé */}
|
||||
@@ -263,7 +266,7 @@ export default function InteretsChart({ rows, netMode }) {
|
||||
{hover && (
|
||||
<g>
|
||||
<line x1={hover.x} y1={PAD.top} x2={hover.x} y2={PAD.top + plotH}
|
||||
stroke="rgba(255,255,255,0.12)" strokeWidth="1" strokeDasharray="3 4" />
|
||||
stroke={GRID} strokeWidth="1" strokeDasharray="3 4" />
|
||||
<circle cx={hover.x} cy={hover.y} r="4.5"
|
||||
fill={COLOR} stroke={BG} strokeWidth="2" />
|
||||
</g>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { useState, useRef, useEffect } from 'react';
|
||||
import { api } from '../api.js';
|
||||
import { useInteretsChart } from '../context/InteretsChartContext.jsx';
|
||||
import { useTheme } from '../context/ThemeContext.jsx';
|
||||
|
||||
const ICONS_BASE = '/api/icons-files/';
|
||||
|
||||
const GRID = 'rgba(255,255,255,0.055)';
|
||||
const LABEL = '#4a5568';
|
||||
const LABEL_LIGHT = '#4a5568';
|
||||
const LABEL_DARK = 'rgba(255,255,255,.65)';
|
||||
const MOIS = ['Jan','Fév','Mar','Avr','Mai','Jun','Jul','Aoû','Sep','Oct','Nov','Déc'];
|
||||
|
||||
function fmtShort(v) {
|
||||
@@ -40,6 +42,8 @@ export default function InteretsMensuelsChart() {
|
||||
netMode,
|
||||
chartInterets, chartCapital, chartCashback,
|
||||
} = useInteretsChart();
|
||||
const { resolved } = useTheme();
|
||||
const LABEL = resolved === 'dark' ? LABEL_DARK : LABEL_LIGHT;
|
||||
|
||||
// État local uniquement
|
||||
const [hovered, setHovered] = useState(null);
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { useMemo, useState, useRef } from 'react';
|
||||
|
||||
/* ── Constantes ─────────────────────────────────────────────── */
|
||||
const BLUE = '#4fa8e8';
|
||||
const BG = '#070c15';
|
||||
const GRID = 'rgba(255,255,255,0.055)';
|
||||
const LABEL = '#4a5568';
|
||||
/* Couleurs alignées sur le thème actif (Admin > Général, cf.
|
||||
* project_theme_interface.md) plutôt que fixées en dur — même mécanisme
|
||||
* que InteretsDonutChart.jsx (attributs de présentation SVG fill/stroke
|
||||
* acceptant var(--...), supporté nativement par les navigateurs modernes). */
|
||||
const BLUE = 'var(--primary)';
|
||||
const BG = 'var(--surface)';
|
||||
const GRID = 'var(--border)';
|
||||
const LABEL = 'var(--text-muted)';
|
||||
|
||||
const RANGES = ['1J', '7J', '1M', '3M', 'YTD', '1A', 'TOUT'];
|
||||
const MOIS_COURT = ['jan.','fév.','mar.','avr.','mai','juin','juil.','août','sep.','oct.','nov.','déc.'];
|
||||
@@ -292,7 +296,7 @@ export default function InvChart({ rows, remboursements, reinvestissements, plat
|
||||
{/* Ligne zéro */}
|
||||
{yZero && yZero > PAD.top && yZero < PAD.top + plotH && (
|
||||
<line x1={PAD.left} y1={yZero} x2={W - PAD.right} y2={yZero}
|
||||
stroke="rgba(255,255,255,0.18)" strokeWidth="1" />
|
||||
stroke={GRID} strokeWidth="1" />
|
||||
)}
|
||||
|
||||
{/* Aire dégradée + courbe */}
|
||||
@@ -316,7 +320,7 @@ export default function InvChart({ rows, remboursements, reinvestissements, plat
|
||||
{hover && (
|
||||
<g>
|
||||
<line x1={hover.x} y1={PAD.top} x2={hover.x} y2={PAD.top + plotH}
|
||||
stroke="rgba(255,255,255,0.12)" strokeWidth="1" strokeDasharray="3 4" />
|
||||
stroke={GRID} strokeWidth="1" strokeDasharray="3 4" />
|
||||
<circle cx={hover.x} cy={hover.y} r="4.5"
|
||||
fill={BLUE} stroke={BG} strokeWidth="2" />
|
||||
</g>
|
||||
|
||||
@@ -1,3 +1,12 @@
|
||||
/* Logo Crowdlending — carré aligné sur le thème d'interface actif
|
||||
* (Admin > Général, cf. project_theme_interface.md) : le fond suit
|
||||
* var(--primary), les 3 barres restent blanches à opacités variées
|
||||
* pour la profondeur, et la flèche (accent "croissance") garde un
|
||||
* vert dédié via var(--sidebar-accent) — stable quel que soit le
|
||||
* thème de couleur choisi (non surchargé par les 9 thèmes) — plutôt
|
||||
* que fixé en dur. Même mécanisme que InvChart.jsx /
|
||||
* InteretsDonutChart.jsx (attributs de présentation SVG fill
|
||||
* acceptant var(--...), supporté nativement). */
|
||||
export default function Logo({ size = 32 }) {
|
||||
return (
|
||||
<svg
|
||||
@@ -8,11 +17,11 @@ export default function Logo({ size = 32 }) {
|
||||
style={{ display: 'block', flexShrink: 0 }}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<rect width="48" height="48" rx="10" fill="#1e3a8a" />
|
||||
<rect x="7" y="31" width="9" height="11" rx="2" fill="#93c5fd" />
|
||||
<rect x="20" y="21" width="9" height="21" rx="2" fill="#60a5fa" />
|
||||
<rect width="48" height="48" rx="10" fill="var(--primary)" />
|
||||
<rect x="7" y="31" width="9" height="11" rx="2" fill="white" fillOpacity="0.55" />
|
||||
<rect x="20" y="21" width="9" height="21" rx="2" fill="white" fillOpacity="0.78" />
|
||||
<rect x="33" y="11" width="9" height="31" rx="2" fill="white" />
|
||||
<polygon points="37.5,4 44,12 31,12" fill="#4ade80" />
|
||||
<polygon points="37.5,4 44,12 31,12" fill="var(--sidebar-accent)" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -28,6 +28,7 @@ export default function PageIcon({ name, size = 40, style }) {
|
||||
return (
|
||||
<img
|
||||
src={`${ICONS_BASE}${filename}`}
|
||||
className="app-lib-icon"
|
||||
width={size}
|
||||
height={size}
|
||||
alt=""
|
||||
@@ -37,7 +38,7 @@ export default function PageIcon({ name, size = 40, style }) {
|
||||
verticalAlign: 'middle',
|
||||
marginRight: 10,
|
||||
objectFit: 'contain',
|
||||
opacity: 0.85,
|
||||
opacity: 1,
|
||||
...style,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { useMemo, useState, useRef, useEffect } from 'react';
|
||||
|
||||
/* ── Constantes ─────────────────────────────────────────────── */
|
||||
const GOLD = '#4fa8e8'; // bleu ciel — accord avec le thème navy du site
|
||||
const BG = '#070c15';
|
||||
const GRID = 'rgba(255,255,255,0.055)';
|
||||
const LABEL = '#4a5568';
|
||||
/* Couleurs alignées sur le thème actif (Admin > Général, cf.
|
||||
* project_theme_interface.md) plutôt que fixées en dur — même mécanisme
|
||||
* que InvChart.jsx / InteretsDonutChart.jsx. */
|
||||
const GOLD = 'var(--primary)';
|
||||
const BG = 'var(--surface)';
|
||||
const GRID = 'var(--border)';
|
||||
const LABEL = 'var(--text-muted)';
|
||||
|
||||
const RANGES = ['1J', '7J', '1M', '3M', 'YTD', '1A', 'TOUT'];
|
||||
|
||||
@@ -254,7 +257,7 @@ export default function SoldeChart({ rows, range: rangeProp, onRangeChange }) {
|
||||
{/* Ligne zéro */}
|
||||
{yZero && yZero > PAD.top && yZero < PAD.top + plotH && (
|
||||
<line x1={PAD.left} y1={yZero} x2={W - PAD.right} y2={yZero}
|
||||
stroke="rgba(255,255,255,0.18)" strokeWidth="1" />
|
||||
stroke={GRID} strokeWidth="1" />
|
||||
)}
|
||||
|
||||
{/* Remplissage dégradé */}
|
||||
@@ -280,7 +283,7 @@ export default function SoldeChart({ rows, range: rangeProp, onRangeChange }) {
|
||||
{hover && (
|
||||
<g>
|
||||
<line x1={hover.x} y1={PAD.top} x2={hover.x} y2={PAD.top + plotH}
|
||||
stroke="rgba(255,255,255,0.12)" strokeWidth="1" strokeDasharray="3 4" />
|
||||
stroke={GRID} strokeWidth="1" strokeDasharray="3 4" />
|
||||
<circle cx={hover.x} cy={hover.y} r="4.5"
|
||||
fill={GOLD} stroke={BG} strokeWidth="2" />
|
||||
</g>
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* ThemeSelect — sélecteur déroulant pour un choix unique avec pastille de
|
||||
* couleur (ex : thème de couleur de l'interface, Admin > Général).
|
||||
* Même schéma d'interaction que CategorySelect (bouton déclencheur +
|
||||
* dropdown, fermeture au clic extérieur / scroll / resize), en position
|
||||
* absolue (pas de portail fixed : utilisé dans une page classique, pas
|
||||
* dans une modale au overflow contraint).
|
||||
*
|
||||
* Props :
|
||||
* value : string — value sélectionnée
|
||||
* onChange : (value: string) => void
|
||||
* options : { value, label, swatch }[] — liste des choix
|
||||
*/
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
export default function ThemeSelect({ value, onChange, options = [] }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const wrapRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const close = (e) => {
|
||||
if (wrapRef.current?.contains(e.target)) return;
|
||||
setOpen(false);
|
||||
};
|
||||
document.addEventListener('mousedown', close);
|
||||
return () => document.removeEventListener('mousedown', close);
|
||||
}, [open]);
|
||||
|
||||
const current = options.find(o => o.value === value) || options[0];
|
||||
|
||||
return (
|
||||
<div ref={wrapRef} className="theme-select-wrap">
|
||||
<button
|
||||
type="button"
|
||||
className={`theme-select-trigger${open ? ' open' : ''}`}
|
||||
onClick={() => setOpen(o => !o)}
|
||||
aria-haspopup="listbox"
|
||||
aria-expanded={open}
|
||||
>
|
||||
<span className="theme-select-swatch" style={{ background: current?.swatch }} aria-hidden="true" />
|
||||
<span className="theme-select-label">{current?.label || 'Choisir…'}</span>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"
|
||||
style={{ flexShrink: 0, marginLeft: 'auto', transition: 'transform .15s', transform: open ? 'rotate(180deg)' : 'rotate(0deg)' }}
|
||||
aria-hidden="true">
|
||||
<path d="M6 9l6 6 6-6" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
{open && (
|
||||
<div className="theme-select-dropdown" role="listbox">
|
||||
{options.map(({ value: v, label, swatch }) => {
|
||||
const active = v === value;
|
||||
return (
|
||||
<button
|
||||
key={v}
|
||||
type="button"
|
||||
role="option"
|
||||
aria-selected={active}
|
||||
className={`theme-select-item${active ? ' active' : ''}`}
|
||||
onClick={() => { onChange(v); setOpen(false); }}
|
||||
>
|
||||
<span className="theme-select-swatch" style={{ background: swatch }} aria-hidden="true" />
|
||||
<span className="theme-select-item-label">{label}</span>
|
||||
{active && (
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"
|
||||
style={{ marginLeft: 'auto', color: 'var(--accent)' }} aria-hidden="true">
|
||||
<polyline points="20 6 9 17 4 12" />
|
||||
</svg>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -56,7 +56,7 @@ function UserAvatar({ user, size = 36 }) {
|
||||
return (
|
||||
<div style={{
|
||||
width: size, height: size, borderRadius: '50%',
|
||||
background: 'linear-gradient(135deg, #1e40af 0%, #1e3a8a 100%)',
|
||||
background: 'linear-gradient(135deg, var(--primary) 0%, var(--primary-hover) 100%)',
|
||||
border: '2px solid rgba(74,222,128,.5)',
|
||||
color: '#fff', display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
fontWeight: 700, fontSize: Math.round(size * 0.38), flexShrink: 0,
|
||||
@@ -70,8 +70,8 @@ function UserAvatar({ user, size = 36 }) {
|
||||
function MemberAvatar({ member, size = 28 }) {
|
||||
const isEntreprise = member?.type === 'entreprise';
|
||||
const bg = isEntreprise
|
||||
? 'linear-gradient(135deg, #3730a3, #4338ca)'
|
||||
: 'linear-gradient(135deg, #1e3a8a, #1e40af)';
|
||||
? 'linear-gradient(135deg, var(--avatar-entreprise-start), var(--avatar-entreprise-end))'
|
||||
: 'linear-gradient(135deg, var(--primary-hover), var(--primary))';
|
||||
return (
|
||||
<div style={{
|
||||
width: size, height: size, borderRadius: '50%',
|
||||
@@ -89,7 +89,7 @@ function TeamBadge({ size = 28 }) {
|
||||
return (
|
||||
<div className="team-badge" style={{
|
||||
width: size, height: size, borderRadius: '50%',
|
||||
background: '#1e3a8a',
|
||||
background: 'var(--primary-hover)',
|
||||
border: '1.5px solid rgba(255,255,255,.25)',
|
||||
color: '#fff',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
|
||||
Reference in new issue
Block a user