✨ Rework du comparateur de statut (#2442)
* Ajoute les accidents du travail et les congés maternité paternité au comparateur de statuts * Ajoute des références sur le calcul du la retraite * Corrige les calculs des droits maladie maternité paternité adoption * Ajoute les pensions d'invalidité * Ajoute le capital décès * Ajoute les rentes ATMP et la pension de reversion * Met à jour le Changelog, les traductions, et tutti quanti * feat: Redécoupage fichiers * feat: Ajoute le mode tab à ToggleGroup * feat: Ajoute mode fullWidth à Simulation * feat: Ajoute une description + lien vers doc * feat: Modifie bouton modifier * feat: Ajoute focusstyle * feat: Ajoute l'icon edit * feat: Ajoute composants à Comparateur * feat: Add StatusCard and icons * feat: Continue implémentation StatusCard * feat: Ajoute icônes, checklist * feat: Ajoute checklist dans les cards de statut * feat: retire id * feat: Ajoute Détails et retraite santé * feat: Ajoute Accident du t * feat: Ajoute les parties manquantes * feat: Améliore liste, retire erreur ts * fix: corrige mise en page * feat: Ajoute le contenu du modal aller plus loin * feat: Retire underline en mode light * feat: Modifie couleur questions, applique modif partie bleue sur tous les simu * feat: Améliore responsiveness * feat: Ajoute impots * feat: Peu de progrès.. * fix small * feat: Implémentation nouveaux droits * feat: Aller plus loin fonctionne * feat: Prise en compte des paramètres allerplus loin * feat: Ajoute DetailsRowCard * feat: Début remplaçage * feat: Passe les engine aux when + cleaning * feat: Commente tester éligibilité * feat: Ajoute Tooltip + warning icon * feat: Ajoute seuils micro + tooltip * feat: Creation WarningTooltip * feat: Corrections + ajouter warning sasu * feat: Ajoute déplier/plier * rebase * fix: Type errors * fix: Open/Close all accordion * fix: E2E tests * feat: cache certains label si la règle n'est pas applicable * feat: Dynamic best option * refacto * Ajoute trad à la mano * feat: Ajoute chevron déplier + modifie structure aller plus loin * feat: Corrige les copy/paste + ajoute tooltip * fix: Best option tooltips * improve tooltip id logic * fix: StatusCard tooltip id * feat: Réimplémentation du tableau aller plus loin * feat: Ajoute toggle ACRE * feat: Ajoute système sauvegarde d'état d'ouverture * feat: Ajoute un timeout sur le scrollto * wtf * feat: Bestoption Revenuapresimpot * feat Ajoute Drawer * fix: Gestion état champ acre * fix: Scroll issue on confirm * fix: Repare clickoutside + améliore doc * feat: Ajoute label * feat: Affine ACRE + fix liens doc * fix: Save accordion state + disable it * feat: Ajoute CasParticuliers context * fix * feat: Affiche la documentation dans une modale * chore: Rebase + fix button color * feat: Ajoute valeur default force theme * fix: Mobile style * feat: Améliorations style mobile * feat: Amélioration darkmode comparateur * feat: Améliore accessibilité + fix doc passée ei ae * chore: clean * fix: unique id a11y error * fix: Fix divers * fix: Test e2e * fix: Dernier fix * fix: Espacement tag en vue mobile Co-authored-by: Johan Girod <johan.girod@beta.gouv.fr>pull/2477/head
parent
dfcfabf214
commit
0d0491bd17
|
@ -1,5 +1,39 @@
|
|||
# Journal des modifications
|
||||
|
||||
## 1.5.0
|
||||
|
||||
Ajoute les droits ouverts à la protection sociale pour les régimes suivants :
|
||||
- indépendants AC/PLNR
|
||||
- auto-entrepreneur hors CIPAV
|
||||
- assimilé salarié
|
||||
|
||||
Les droits suivants ont été implémentés :
|
||||
- Indemnités journalières et délai d’attente en cad d’arrêt maladie
|
||||
- Indemnités journalières pour les accidents du travail et maladie professionnelle
|
||||
- Indemnités journalières et forfaitaire pour les congés maternité paternité adoption
|
||||
- Rentes, capital décès, pension de reversion et d’invalidité
|
||||
|
||||
|
||||
### Détails
|
||||
|
||||
Ajout des règles suivantes :
|
||||
- dirigeant . indépendant . cotisations et contributions . invalidité et décès
|
||||
- protection sociale . maladie . raam
|
||||
- protection sociale . maladie . maternité paternité adoption . *
|
||||
- protection sociale . maladie . arrêt maladie . *
|
||||
- protection sociale . invalidité et décès . *
|
||||
|
||||
Renomme les règles suivantes :
|
||||
- protection sociale . retraite . base . cotisée . revenu salarié -> protection sociale . retraite . base . cotisée . salarié
|
||||
- protection sociale . retraite . base . cotisée . revenu indépendant -> protection sociale . retraite . base . cotisée . indépendant
|
||||
- protection sociale . accidents du travail et maladies professionnelles -> protection sociale . maladie . accidents du travail et maladies professionnelles
|
||||
|
||||
|
||||
Supression des règles suivantes :
|
||||
- protection sociale . maladie . ATMP
|
||||
|
||||
*Note : l’espace de nom `protection social` étant taggué comme « experimental », ces changements cassants ne provoquent pas de montée de version majeure.
|
||||
|
||||
## 1.4.2
|
||||
- Augmentation du plafond de taux réduit pour l'impôt sur les sociétés (merci @fmata)
|
||||
|
||||
|
|
|
@ -1304,6 +1304,7 @@ dirigeant . indépendant . cotisations et contributions . retraite complémentai
|
|||
dirigeant . indépendant . cotisations et contributions . invalidité et décès:
|
||||
produit:
|
||||
assiette:
|
||||
nom: assiette
|
||||
valeur: assiette des cotisations
|
||||
plancher: assiette minimale . retraite
|
||||
plafond: plafond sécurité sociale
|
||||
|
|
|
@ -59,6 +59,11 @@ entreprise . chiffre d'affaires:
|
|||
identifiant court: CA
|
||||
résumé: Montant total des recettes brutes (hors taxe)
|
||||
unité: €/an
|
||||
description: |
|
||||
### Chiffre d'affaires estimé
|
||||
Le chiffre d'affaires est la <strong>somme des montants des ventes réalisées
|
||||
pendant votre exercice comptable</strong> (un an) : CA = prix de vente x quantités
|
||||
vendues.
|
||||
variations:
|
||||
- si: dirigeant . auto-entrepreneur
|
||||
alors: dirigeant . auto-entrepreneur . chiffre d'affaires
|
||||
|
|
|
@ -288,10 +288,21 @@ entreprise . imposition . régime . micro-entreprise . revenu abattu:
|
|||
entreprise . imposition . régime . micro-entreprise . alerte seuil dépassés:
|
||||
type: notification
|
||||
sévérité: avertissement
|
||||
formule: chiffre d'affaires . seuil micro dépassé
|
||||
formule: chiffre d'affaires . seuil micro . dépassé
|
||||
description: Le seuil annuel de chiffre d'affaires pour le régime micro-fiscal est dépassé. [En savoir plus](/documentation/entreprise/chiffre-d'affaires/seuil-micro-dépassé)
|
||||
|
||||
entreprise . chiffre d'affaires . seuil micro dépassé:
|
||||
entreprise . chiffre d'affaires . seuil micro:
|
||||
experimental: oui
|
||||
|
||||
entreprise . chiffre d'affaires . seuil micro . libérale:
|
||||
unité: €/an
|
||||
valeur: 72600 €/an
|
||||
|
||||
entreprise . chiffre d'affaires . seuil micro . total:
|
||||
unité: €/an
|
||||
valeur: 176200 €/an
|
||||
|
||||
entreprise . chiffre d'affaires . seuil micro . dépassé:
|
||||
experimental: oui
|
||||
applicable si: imposition . IR
|
||||
description: |
|
||||
|
@ -330,8 +341,8 @@ entreprise . chiffre d'affaires . seuil micro dépassé:
|
|||
# économie collaborative). Il faudrait référencer la même valeur partout où
|
||||
# elle est utilisée.
|
||||
une de ces conditions:
|
||||
- entreprise . chiffre d'affaires > 176200 €/an
|
||||
- entreprise . chiffre d'affaires . service > 72600 €/an
|
||||
- entreprise . chiffre d'affaires > total
|
||||
- entreprise . chiffre d'affaires . service > service
|
||||
|
||||
entreprise . imposition . régime . déclaration contrôlée:
|
||||
applicable si: IR . type de bénéfices . BNC
|
||||
|
|
|
@ -29,7 +29,7 @@ protection sociale . retraite:
|
|||
références:
|
||||
Panorama des régimes de retraites: https://travail-emploi.gouv.fr/retraite/le-systeme-de-retraite-actuel/
|
||||
'Retraites de base et complémentaire dans le privé : quelles différences ?': https://www.service-public.fr/particuliers/vosdroits/F12389
|
||||
|
||||
'Régime de retraite des travailleurs indépendants': 'https://entreprendre.service-public.fr/vosdroits/F33841'
|
||||
protection sociale . retraite . trimestres:
|
||||
titre: trimestres validés
|
||||
|
||||
|
@ -58,26 +58,32 @@ protection sociale . retraite . base:
|
|||
Le montant de votre pension pour la retraite de base est calculé à partir la moyenne de vos revenus des 25 meilleures années.
|
||||
|
||||
Cet estimation de votre pension de retraite est calculée en se basant sur les principes suivants :
|
||||
- La rémunération calculée correspond à celle de vos 25 meilleures années
|
||||
- La rémunération calculée dans le simulateur correspond à celle de vos 25 meilleures années
|
||||
- Vous avez cotisé suffisement de trimestres et vous partez à l'âge requis pour bénéficier du taux plein
|
||||
arrondi: oui
|
||||
unité: €/mois
|
||||
produit:
|
||||
assiette: base . cotisée
|
||||
taux: 50%
|
||||
références:
|
||||
'Retraites de base et complémentaire dans le privé : quelles différences ?': https://www.service-public.fr/particuliers/vosdroits/F12389
|
||||
'Assurance Retraite de la Sécurité sociale': https://www.lassuranceretraite.fr/
|
||||
'Calcul de la retraite du salarié du secteur privé': 'https://www.service-public.fr/particuliers/vosdroits/F21552'
|
||||
'Régime de retraite des travailleurs indépendants': 'https://entreprendre.service-public.fr/vosdroits/F33841'
|
||||
|
||||
protection sociale . retraite . base . cotisée:
|
||||
titre: revenu cotisés pour la retraite de base
|
||||
unité: €/mois
|
||||
arrondi: oui
|
||||
variations:
|
||||
- si: dirigeant . indépendant
|
||||
alors: revenu indépendant
|
||||
alors: indépendant
|
||||
- si: dirigeant . auto-entrepreneur
|
||||
alors: revenu auto-entrepreneur
|
||||
- sinon: revenu salarié
|
||||
- sinon: salarié
|
||||
plafond: plafond sécurité sociale
|
||||
avec:
|
||||
revenu salarié:
|
||||
salarié:
|
||||
titre: revenu salarié
|
||||
valeur: salarié . cotisations . vieillesse . salarié / (salarié . cotisations . vieillesse . salarié . plafonnée . taux + salarié . cotisations . vieillesse . salarié . déplafonnée . taux)
|
||||
références:
|
||||
Article R351-9 du Code de la sécurité sociale: https://www.legifrance.gouv.fr/codes/article_lc/LEGIARTI000028751530/2014-03-21
|
||||
|
@ -88,7 +94,8 @@ protection sociale . retraite . base . cotisée:
|
|||
avec:
|
||||
entreprise . imposition . régime . micro-entreprise . revenu abattu . plancher abattement: non
|
||||
|
||||
revenu indépendant:
|
||||
indépendant:
|
||||
titre: revenu indépendant
|
||||
valeur: dirigeant . indépendant . cotisations et contributions . retraite de base / dirigeant . indépendant . cotisations et contributions . retraite de base . taux
|
||||
références:
|
||||
Article R351-9 du code de la sécurité sociale: https://www.legifrance.gouv.fr/codes/article_lc/LEGIARTI000028751530/2014-03-21
|
||||
|
@ -366,6 +373,22 @@ protection sociale . maladie:
|
|||
Ce qui est remboursé pour tout le monde: https://www.ameli.fr/assure/remboursements/rembourse
|
||||
Rapport d'activité de l'assurance maladie 2017 (PDF): https://assurance-maladie.ameli.fr/sites/default/files/ra-2017_agir-ensemble-proteger-chacun.pdf
|
||||
Rapport OCDE sur l'esperance de vie dans les différents pays: https://read.oecd-ilibrary.org/social-issues-migration-health/health-at-a-glance-europe-2018_health_glance_eur-2018-en#page89
|
||||
avec:
|
||||
'[privé] plancher indemnités salarié': 1015 heure * SMIC . horaire
|
||||
'[privé] abattement forfaitaire salarié': 21%
|
||||
'raam':
|
||||
titre: Revenu d’activité annuel moyen
|
||||
valeur:
|
||||
variations:
|
||||
- si: dirigeant . indépendant
|
||||
alors: dirigeant . indépendant . cotisations et contributions . indemnités journalières maladie . assiette
|
||||
- si: dirigeant . auto-entrepreneur
|
||||
alors: dirigeant . auto-entrepreneur . impôt . revenu imposable
|
||||
plafond:
|
||||
variations:
|
||||
- si: entreprise . activité . nature . libérale . réglementée
|
||||
alors: 3 * plafond sécurité sociale
|
||||
- sinon: plafond sécurité sociale
|
||||
|
||||
protection sociale . maladie . arrêt maladie:
|
||||
titre:
|
||||
|
@ -390,45 +413,41 @@ protection sociale . maladie . arrêt maladie:
|
|||
non applicable si: arrêt maladie = 0
|
||||
|
||||
protection sociale . maladie . arrêt maladie . salarié:
|
||||
références:
|
||||
'Arrêt de travail pour maladie : les indemnités journalières du salarié': https://www.ameli.fr/assure/remboursements/indemnites-journalieres/arret-maladie-salarie
|
||||
'Arrêt maladie : indemnités journalières versées au salarié': https://www.service-public.fr/particuliers/vosdroits/F3053
|
||||
non applicable si:
|
||||
une de ces conditions:
|
||||
- dirigeant . indépendant
|
||||
- dirigeant . auto-entrepreneur
|
||||
|
||||
avec:
|
||||
conditions:
|
||||
avec:
|
||||
revenu:
|
||||
valeur: salarié . cotisations . assiette * 6 mois > plancher
|
||||
revenu: salarié . cotisations . assiette * 6 mois > plancher indemnités salarié
|
||||
délai d'attente:
|
||||
description: |
|
||||
Pour pouvoir prétendre à une indemnisation pour maladie au titre de votre activité professionnelle, vous devez justifier d’un délai d’affiliation continus dans cette activité. Ce dernier dépend de votre rémunération des mois précédents.
|
||||
|
||||
remplace: arrêt maladie . délai d'attente
|
||||
applicable si: conditions . revenu
|
||||
valeur: (plancher / salarié . cotisations . assiette) + 0.5
|
||||
valeur: (plancher indemnités salarié / salarié . cotisations . assiette) + 0.5
|
||||
arrondi: oui
|
||||
'[privé] plancher': 1015 heure * SMIC . horaire
|
||||
|
||||
références:
|
||||
Quels sont les critères pour être indemnisé en cas de maladie ?: https://www.ameli.fr/tarn/assure/remboursements/indemnites-journalieres/arret-maladie-salarie#text_2632
|
||||
Quels sont les critères pour être indemnisé en cas de maladie ?: https://www.ameli.fr/assure/remboursements/indemnites-journalieres/arret-maladie-salarie#text_2632
|
||||
|
||||
indemnités:
|
||||
titre: indemnités journalières
|
||||
applicable si: conditions . revenu
|
||||
unité: €/jour
|
||||
description: |
|
||||
L'indemnité journalière que vous recevrez pendant votre arrêt de travail est égale à 50 % de votre salaire journalier de base. Celui-ci est calculé sur la moyenne des salaires bruts des 3 derniers mois précédant votre arrêt de travail (12 mois en cas d'activité saisonnière).
|
||||
produit:
|
||||
assiette:
|
||||
valeur: salarié . cotisations . assiette / 91.25 jour/trimestre
|
||||
valeur: salarié . cotisations . assiette
|
||||
unité: €/jour
|
||||
plafond: 1.8 * SMIC
|
||||
taux: 50%
|
||||
notes: |
|
||||
- Vu que le simulateur ne permet pas encore la conversion de période vers le jour, on multiplie le salaire moyen par 3 pour avoir le salaire trimestriel, puis on le divise par 91.25, conformément à la fiche service-public.fr
|
||||
- Pour les salarié, votre entreprise est peut-être soumise à une convention collective de branche professionnelle qui assure le maintien de votre salaire intégral ou partiel pendant votre arrêt de travail pour maladie. Elle peut aussi avoir conclu un accord interne à l’entreprise qui prévoit ce maintien, appelé subrogation. Renseignez-vous auprès du service qui gère la paye dans votre entreprise.
|
||||
références:
|
||||
'Arrêt de travail pour maladie : les indemnités journalières du salarié': https://www.ameli.fr/assure/remboursements/indemnites-journalieres/arret-maladie-salarie
|
||||
'Arrêt maladie : indemnités journalières versées au salarié': https://www.service-public.fr/particuliers/vosdroits/F3053
|
||||
|
||||
protection sociale . maladie . arrêt maladie . indépendant:
|
||||
applicable si:
|
||||
|
@ -442,7 +461,7 @@ protection sociale . maladie . arrêt maladie . indépendant:
|
|||
|
||||
Depuis le 1er janvier 2022, il est donc possible de percevoir des indemnités journalières pour maladie et/ou pour maternité au titre de son ancienne activité (quel que soit le régime auquel on était affilié).
|
||||
référence:
|
||||
Comment bénéficier d'indemnités liées à son ancien régime: https://www.ameli.fr/tarn/assure/actualites/indemnites-maladie-et-maternite-du-nouveau-pour-certains-travailleurs-independants
|
||||
Comment bénéficier d'indemnités liées à son ancien régime: https://www.ameli.fr/assure/actualites/indemnites-maladie-et-maternite-du-nouveau-pour-certains-travailleurs-independants
|
||||
avec:
|
||||
revenu: raam > 10% * plafond sécurité sociale
|
||||
délai d'attente:
|
||||
|
@ -456,6 +475,7 @@ protection sociale . maladie . arrêt maladie . indépendant:
|
|||
'Artisan/commerçant : quels sont les critères pour être indemnisé en cas de maladie ?': https://www.ameli.fr/assure/remboursements/indemnites-journalieres/arret-maladie-artisans-commercants#text_124972#text_124921
|
||||
'Profession libérale : quels sont les critères pour être indemnisé en cas de maladie ?': 'https://www.ameli.fr/assure/remboursements/indemnites-journalieres/arret-maladie-profession-liberale#text_170646'
|
||||
indemnités:
|
||||
titre: indemnités journalières
|
||||
applicable si: conditions . revenu
|
||||
description: |
|
||||
L'indemnité journalière que vous recevrez pendant votre arrêt de travail est égale à 1/730e de votre revenu d’activité annuel moyen (Raam) (1). Celui-ci est calculé sur la moyenne de vos revenus cotisés des 3 années civiles précédant la date de votre arrêt de travail.
|
||||
|
@ -465,26 +485,100 @@ protection sociale . maladie . arrêt maladie . indépendant:
|
|||
assiette: raam
|
||||
facteur: 1 an / 730 jour
|
||||
|
||||
'[privé] raam':
|
||||
titre: Revenu d’activité annuel moyen
|
||||
valeur:
|
||||
variations:
|
||||
- si: dirigeant . indépendant
|
||||
alors: dirigeant . indépendant . cotisations et contributions . indemnités journalières maladie . assiette
|
||||
- si: dirigeant . auto-entrepreneur
|
||||
alors: dirigeant . auto-entrepreneur . impôt . revenu imposable
|
||||
plafond:
|
||||
variations:
|
||||
- si: entreprise . activité . nature . libérale . réglementée
|
||||
alors: 3 * plafond sécurité sociale
|
||||
- sinon: plafond sécurité sociale
|
||||
|
||||
références:
|
||||
Quelles indemnités journalières pour les artisans/commerçants: https://www.ameli.fr/assure/remboursements/indemnites-journalieres/arret-maladie-artisans-commercants#text_124972
|
||||
Quelles indemnités journalières pour les professions libérales: https://www.ameli.fr/assure/remboursements/indemnites-journalieres/arret-maladie-profession-liberale#text_170670
|
||||
|
||||
protection sociale . maladie . ATMP:
|
||||
titre: Accident du travail et maladie professionnelle
|
||||
protection sociale . maladie . maternité paternité adoption:
|
||||
titre: indemnités congé maternité paternité adoption
|
||||
références:
|
||||
'Paternité et accueil de l’enfant : vos indemnités journalières': https://www.ameli.fr/assure/remboursements/indemnites-journalieres/conge-paternite-accueil-enfant
|
||||
'Les prestations maternité des travailleuses indépendantes et des conjointes collaboratrices': https://www.ameli.fr/assure/remboursements/indemnites-journalieres/prestations-maternite-independantes-conjointes-collaboratric
|
||||
'Congé d’adoption : les indemnités journalières': https://www.ameli.fr/assure/remboursements/indemnites-journalieres/conge-adoption
|
||||
'Simulateur maternité paternité adoption': https://www.ameli.fr/assure/simulateur-maternite-paternite
|
||||
somme:
|
||||
- salarié . indemnités
|
||||
- indépendant . indemnités
|
||||
|
||||
avec:
|
||||
délai d'attente:
|
||||
description: |
|
||||
## Maternité
|
||||
Vous devez justifiez de 10 mois d’affiliation à la date prévue de votre accouchement,
|
||||
et cesser toute activité professionnelle pendant la période de perception et au moins pendant 8 semaines dont 6 après l’accouchement
|
||||
## Paternité / Adoption
|
||||
Pour en bénéficier, vous devez justifier de 10 mois d’affiliation à la naissance / à l’adoption.
|
||||
valeur: 10 mois
|
||||
|
||||
allocation forfaitaire de repos maternel:
|
||||
non applicable si: oui
|
||||
allocation forfaitaire de repos adoption:
|
||||
non applicable si: oui
|
||||
|
||||
salarié:
|
||||
applicable si: salarié
|
||||
|
||||
avec:
|
||||
indemnités:
|
||||
titre: indemnités journalières
|
||||
applicable si: arrêt maladie . salarié . conditions . revenu
|
||||
unité: €/jour
|
||||
description: |
|
||||
L'indemnité journalière que vous recevrez pendant votre arrêt de travail est égale à 50 % de votre salaire journalier de base. Celui-ci est calculé sur la moyenne des salaires bruts des 3 derniers mois précédant votre arrêt de travail (12 mois en cas d'activité saisonnière).
|
||||
produit:
|
||||
assiette:
|
||||
valeur: salarié . cotisations . assiette
|
||||
unité: €/jour
|
||||
plafond:
|
||||
unité: €/jour
|
||||
arrondi: 2 décimales
|
||||
le minimum de:
|
||||
- plafond sécurité sociale
|
||||
- salarié . cotisations . assiette - abattement forfaitaire salarié
|
||||
plancher: invalidité et décès . pension invalidité . minimum salarié
|
||||
références:
|
||||
'Congé maternité : les indemnités journalières pour les salariées ': https://www.ameli.fr/assure/remboursements/indemnites-journalieres/conge-maternite-salariee
|
||||
|
||||
indépendant:
|
||||
applicable si:
|
||||
une de ces conditions:
|
||||
- dirigeant . indépendant
|
||||
- dirigeant . auto-entrepreneur
|
||||
|
||||
avec:
|
||||
indemnités:
|
||||
titre: indemnités journalières forfaitaires
|
||||
unité: €/jour
|
||||
produit:
|
||||
assiette: plafond sécurité sociale
|
||||
facteur: 50%
|
||||
abattement:
|
||||
applicable si: raam < 10% * plafond sécurité sociale
|
||||
valeur: 90%
|
||||
références:
|
||||
'Les indemnités journalières forfaitaires maternité des travailleuses indépendantes': https://www.ameli.fr/assure/remboursements/indemnites-journalieres/prestations-maternite-independantes-conjointes-collaboratric#text_125695
|
||||
'Paternité et accueil de l’enfant : les indemnités journalières pour les travailleurs indépendants': https://www.ameli.fr/assure/remboursements/indemnites-journalieres/conge-paternite-accueil-enfant#text_114763
|
||||
|
||||
allocation forfaitaire de repos maternel:
|
||||
remplace: allocation forfaitaire de repos maternel
|
||||
description: |
|
||||
Elle est versée pour moitié au début du congé et pour moitié à la fin de la période obligatoire de cessation d’activité de 8 semaines. La totalité du montant de l’allocation est versée après l’accouchement lorsque celui-ci a lieu avant la fin du 7e mois de la grossesse.
|
||||
produit:
|
||||
assiette:
|
||||
variations:
|
||||
- si: raam < 10% * plafond sécurité sociale
|
||||
alors: 10% * plafond sécurité sociale
|
||||
- sinon: plafond sécurité sociale
|
||||
facteur: 1 mois
|
||||
référence:
|
||||
'L’allocation forfaitaire de repos maternel': https://www.ameli.fr/assure/remboursements/indemnites-journalieres/prestations-maternite-independantes-conjointes-collaboratric#text_125689
|
||||
'Article L623-1 du code de la sécurité sociale': https://www.legifrance.gouv.fr/codes/article_lc/LEGIARTI000042685502/
|
||||
|
||||
allocation forfaitaire de repos adoption:
|
||||
remplace: allocation forfaitaire de repos adoption
|
||||
valeur: 50% * allocation forfaitaire de repos maternel
|
||||
références:
|
||||
'Article L623-1 du code de la sécurité sociale': https://www.legifrance.gouv.fr/codes/article_lc/LEGIARTI000042685502/
|
||||
|
||||
protection sociale . invalidité et décès:
|
||||
icônes: 🦽
|
||||
|
@ -498,6 +592,158 @@ protection sociale . invalidité et décès:
|
|||
capital décès (amelie.fr): https://www.ameli.fr/assure/remboursements/pensions-allocations-rentes/deces-proche-capital-deces
|
||||
capital décès (salarié privé): https://www.service-public.fr/particuliers/vosdroits/F3005
|
||||
pension invalidité: https://www.service-public.fr/particuliers/vosdroits/F672
|
||||
avec:
|
||||
pension invalidité:
|
||||
références:
|
||||
Articles R341-4 à R341-6-1 du Code de la sécurité sociale: https://www.legifrance.gouv.fr/codes/id/LEGISCTA000006173390
|
||||
avec:
|
||||
invalidité partielle:
|
||||
unité: €/mois
|
||||
description: Si vous êtes capable d'exercer une activité professionnelle rémunérée, vous êtes classé en 1re catégorie.
|
||||
plancher: minimum
|
||||
produit:
|
||||
assiette: revenu annuel moyen des 10 meilleures années
|
||||
taux: 30%
|
||||
références:
|
||||
'Le montant de votre pension d’invalidité': https://www.ameli.fr/assure/remboursements/pensions-allocations-rentes/invalidite
|
||||
|
||||
invalidité totale:
|
||||
unité: €/mois
|
||||
plancher: minimum
|
||||
description: Si vous ne pouvez plus exercer d'activité professionnelle, vous êtes classé en 2e catégorie.
|
||||
produit:
|
||||
assiette: revenu annuel moyen des 10 meilleures années
|
||||
taux: 50%
|
||||
références:
|
||||
'Le montant de votre pension d’invalidité': https://www.ameli.fr/assure/remboursements/pensions-allocations-rentes/invalidite
|
||||
|
||||
revenu annuel moyen des 10 meilleures années:
|
||||
description: |
|
||||
Depuis le 1er juillet 2016, vous ne pouvez percevoir qu’une seule pension d’invalidité : soit au titre de votre activité salariée, soit au titre de votre activité d’artisan/commerçant. Toutefois, le montant de la pension tient compte de tous vos revenus perçus qu’ils proviennent de votre activité salariée ou de votre activité comme indépendant.
|
||||
somme:
|
||||
- salarié . cotisations . assiette
|
||||
- applicable si: maladie . arrêt maladie . indépendant . conditions . revenu
|
||||
variations:
|
||||
- si: dirigeant . indépendant
|
||||
alors: dirigeant . indépendant . cotisations et contributions . invalidité et décès . assiette
|
||||
- si: dirigeant . auto-entrepreneur
|
||||
alors: dirigeant . auto-entrepreneur . impôt . revenu imposable
|
||||
plafond: plafond sécurité sociale
|
||||
|
||||
'[privé] minimum': non
|
||||
|
||||
minimum salarié:
|
||||
remplace: minimum
|
||||
applicable si: salarié
|
||||
valeur: 309.09 €/mois
|
||||
références:
|
||||
"Montant minimal de la pension d'invalidité de travailleur salarié": https://www.legislation.cnav.fr/Lists/ArticlesBareme/DispForm.aspx?ID=4266&ContentTypeId=0x01007CF8FA8574A1B64CA3888B8B205B3F58
|
||||
'Le montant de votre pension d’invalidité ': https://www.ameli.fr/assure/remboursements/pensions-allocations-rentes/invalidite
|
||||
|
||||
'[privé] minimum indépendant':
|
||||
applicable si: maladie . arrêt maladie . indépendant . conditions . revenu
|
||||
une de ces conditions:
|
||||
- dirigeant . auto-entrepreneur
|
||||
- dirigeant . indépendant
|
||||
remplace:
|
||||
- règle: minimum
|
||||
dans: invalidité partielle
|
||||
par: 486.98 €/mois
|
||||
- règle: minimum
|
||||
dans: invalidité totale
|
||||
par: 686.09 €/mois
|
||||
références:
|
||||
"Montant et versement de la pension d'invalidité": https://www.ameli.fr/assure/remboursements/pensions-allocations-rentes/invalidite
|
||||
|
||||
accidents du travail et maladies professionnelles:
|
||||
applicable si: salarié
|
||||
références:
|
||||
'Incapacité permanente suite à un accident du travail : indemnités et rente': https://www.ameli.fr/tarn/assure/remboursements/pensions-allocations-rentes/incapacite-permanente-suite-accident-travail
|
||||
'Incapacité permanente suite à une maladie professionnelle : indemnités et rentes': https://www.ameli.fr/tarn/assure/remboursements/pensions-allocations-rentes/incapacite-permanente-suite-maladie-professionnelle
|
||||
avec:
|
||||
rente incapacité:
|
||||
titre: Rente incapacité AT/MP
|
||||
description: |
|
||||
Si votre taux d'incapacité permanente suite à un accident du travail ou une maladie professionnelle est supérieur à 10 %, vous percevrez une rente d'incapacité permanente.
|
||||
produit:
|
||||
assiette:
|
||||
barème:
|
||||
assiette: salarié . cotisations . assiette
|
||||
multiplicateur: SMIC
|
||||
tranches:
|
||||
- taux: 100%
|
||||
plafond: 2
|
||||
- taux: 1 / 3
|
||||
plafond: 8
|
||||
taux:
|
||||
barème:
|
||||
assiette:
|
||||
nom: taux incapacité
|
||||
question: Quel taux d'incapacité voulez-vous simuler pour la rente accidents du travail et maladie professionnelle ?
|
||||
plancher: 10%
|
||||
plafond: 100%
|
||||
par défaut: 50%
|
||||
tranches:
|
||||
- taux: 50%
|
||||
plafond: 50%
|
||||
- taux: 150%
|
||||
références:
|
||||
"Accident du travail : indemnisation en cas d'incapacité permanente": https://www.service-public.fr/particuliers/vosdroits/F14840
|
||||
|
||||
rente décès:
|
||||
titre: Rente décès AT/MP
|
||||
description: |
|
||||
Si l'accident du travail entraîne le décès de l'assuré, les proches (conjoint, concubin, partenaire lié par un pacte civil de solidarité (Pacs) - non divorcé ni séparé - enfants, etc.) peuvent bénéficier d'une rente.
|
||||
produit:
|
||||
assiette: salarié . cotisations . assiette
|
||||
taux: 40%
|
||||
références:
|
||||
"Décès d'un salarié suite à un accident de travail ou de trajet : indemnisation des ayants droit": https://www.service-public.fr/particuliers/vosdroits/F14868
|
||||
|
||||
capital décès:
|
||||
unité: €
|
||||
variations:
|
||||
- si: salarié
|
||||
alors: 3681 €
|
||||
- si:
|
||||
une de ces conditions:
|
||||
- dirigeant . indépendant
|
||||
- dirigeant . auto-entrepreneur
|
||||
alors: 20% * plafond sécurité sociale * 1 an
|
||||
|
||||
capital décès . orphelin:
|
||||
applicable si:
|
||||
une de ces conditions:
|
||||
- dirigeant . indépendant
|
||||
- dirigeant . auto-entrepreneur
|
||||
description: |
|
||||
Un capital « orphelin » est versé aux enfants des travailleurs indépendants décédés. Il concerne :
|
||||
|
||||
- les enfants âgés de moins de 16 ans au jour du décès de l’assuré et à sa charge ;
|
||||
- les enfants à la charge du défunt de plus de 16 ans, et de moins de 20 ans, poursuivant leurs études ou leur apprentissage ;
|
||||
- les enfants, quel que soit leur âge, bénéficiaires des allocations instituées en faveur des handicapés.
|
||||
références:
|
||||
'Le capital orphelin pour les enfants des travailleurs indépendants': https://www.ameli.fr/tarn/assure/remboursements/pensions-allocations-rentes/deces-proche-capital-deces#text_76987
|
||||
valeur: 5% * plafond sécurité sociale * 1 an / 1 enfant
|
||||
|
||||
pension de reversion:
|
||||
titre: pension de reversion maximum
|
||||
unité: €/mois
|
||||
description: |
|
||||
Au décès de votre époux(se) ou ex-époux(se), vous pouvez percevoir une pension de réversion.
|
||||
Le versement de la pension est possible, sous certaines conditions, lorsque le défunt exerçait une activité salariée ou non salariée (travailleur indépendant, professionnel libéral, agriculteur).
|
||||
|
||||
La pension est égale à 54 % de la retraite que votre époux(se) ou ex-époux(se) percevait ou aurait pu percevoir (majorations non comprises).
|
||||
références:
|
||||
Pension de réversion: https://www.lassuranceretraite.fr/portail-info/home/actif/travailleur-independant/veuvage/pension-reversion-veuvage.html
|
||||
Montants minimums de la pension de reversion: https://www.legislation.cnav.fr/Pages/bareme.aspx?Nom=retraite_reversion_montant_minimum_bar
|
||||
Pension de réversion - Défunt ayant travaillé dans le privé: https://www.service-public.fr/particuliers/vosdroits/F35774/0?idFicheParent=N378#0
|
||||
plancher:
|
||||
variations:
|
||||
- si: date >= 07/2022
|
||||
alors: 3672.01 €/an
|
||||
- sinon: 3530.78 €/an
|
||||
valeur: 54 % * retraite . base
|
||||
|
||||
protection sociale . assurance chômage:
|
||||
icônes: 💸
|
||||
|
@ -540,38 +786,47 @@ protection sociale . famille:
|
|||
Allocations destinées aux familles: https://www.service-public.fr/particuliers/vosdroits/N156
|
||||
Tout savoir sur les Allocations familiales: https://www.caf.fr/nous-connaitre/qui-sommes-nous
|
||||
|
||||
protection sociale . accidents du travail et maladies professionnelles:
|
||||
protection sociale . maladie . accidents du travail et maladies professionnelles:
|
||||
icônes: ☣️
|
||||
résumé: Offre une couverture complète des maladies ou accidents du travail.
|
||||
description: |
|
||||
L’assurance AT/MP (accident du travail et maladie professionnelle) est la plus ancienne branche de la Sécurité sociale : elle relève de principes qui remontent à l’année 1898 et qui ont été repris dans la loi du 31 décembre 1946.
|
||||
|
||||
[🎞️ Voir la vidéo](https://www.youtube.com/watch?v=NaGI_deZJD8 )
|
||||
|
||||
La cotisation AT/MP couvre les risques accidents du travail, accidents de trajet et maladies professionnelles pour les salariés relevant du régime général.
|
||||
|
||||
Pour connaître les risques professionnels et mettre en place des actions de prévention, le [compte AT/MP](https://www.ameli.fr/entreprise/votre-entreprise/compte-atmp/ouvrir-compte-atmp) est un service ouvert à toutes les entreprises du régime général de la Sécurité sociale.
|
||||
|
||||
En cas d’AT/MP, les soins médicaux et chirurgicaux sont remboursés intégralement dans la limite des tarifs de la Sécurité sociale.
|
||||
Vous avez subi un accident du travail ou êtes atteint d’une maladie professionnelle ?
|
||||
Vos frais médicaux sont pris en charge à 100 %.
|
||||
|
||||
Pour compenser votre perte de salaire, vous pouvez percevoir des indemnités journalières.
|
||||
Si vous êtes déclaré inapte suite à votre accident / maladie, vous pouvez recevoir une indemnité temporaire d'inaptitude.
|
||||
unité: €/jour
|
||||
|
||||
applicable si: salarié
|
||||
produit:
|
||||
assiette:
|
||||
valeur: 5
|
||||
plafond: 83.4% * plafond sécurité sociale
|
||||
taux:
|
||||
nom: Pourcentage du salaire journalier de référence
|
||||
valeur: 60%
|
||||
note: |
|
||||
Le taux est de 80% à partir du 29e jour d'arrêt.
|
||||
avec:
|
||||
indemmnités:
|
||||
produit:
|
||||
assiette:
|
||||
nom: salaire journalier de référence
|
||||
privé: oui
|
||||
valeur: salarié . cotisations . assiette
|
||||
unité: €/jour
|
||||
plafond:
|
||||
arrondi: 2 décimales
|
||||
unité: €/jour
|
||||
le minimum de:
|
||||
- valeur: 0.834% * (plafond sécurité sociale * 1 an) / 1 jour
|
||||
- valeur: salarié . cotisations . assiette - abattement forfaitaire salarié
|
||||
taux: 60%
|
||||
avec:
|
||||
à partir du 29ème jour:
|
||||
produit:
|
||||
assiette: salaire journalier de référence
|
||||
taux: 80%
|
||||
|
||||
références:
|
||||
ameli.fr: https://www.ameli.fr/entreprise/votre-entreprise/cotisation-atmp
|
||||
service-public.fr (AT): https://www.service-public.fr/particuliers/vosdroits/F31881
|
||||
service-public.fr (MP): https://www.service-public.fr/particuliers/vosdroits/F31880
|
||||
Calcul de l'indemnité: https://www.service-public.fr/particuliers/vosdroits/F32148
|
||||
Code de la Sécurité Sociale: https://www.legifrance.gouv.fr/codes/id/LEGISCTA000006156659/2020-12-10/
|
||||
"Comprendre l'assurance AT/MP": https://www.ameli.fr/entreprise/votre-entreprise/cotisation-atmp
|
||||
'Maladie professionnelle : prise en charge et indemnités journalières': https://www.ameli.fr/assure/remboursements/indemnites-journalieres/maladie-professionnelle
|
||||
'Accident du travail : prise en charge et indemnités journalières': https://www.ameli.fr/assure/remboursements/indemnites-journalieres/accident-travail
|
||||
"Qu'est-ce qu'un accident de trajet ?": https://www.service-public.fr/particuliers/vosdroits/F31881
|
||||
"Qu'est-ce qu'une maladie professionnelle ?": https://www.service-public.fr/particuliers/vosdroits/F31880
|
||||
"Accident du travail : indemnités journalières pendant l'arrêt de travail": https://www.service-public.fr/particuliers/vosdroits/F175
|
||||
"Maladie professionnelle : indemnités journalières pendant l'arrêt de travail": https://www.service-public.fr/particuliers/vosdroits/F32148
|
||||
Articles R433-1 à R433-17 du Code de la Sécurité Sociale: https://www.legifrance.gouv.fr/codes/id/LEGISCTA000006156659/2020-12-10/
|
||||
|
||||
protection sociale . formation:
|
||||
icônes: 👩🎓
|
||||
|
|
|
@ -10,8 +10,8 @@ const salaireNetApresImpot = 'input[id="salariérémunérationnetpayéaprèsimp
|
|||
|
||||
describe('Test prerender', function () {
|
||||
const testSimuSalaire = (cy: cyType) => {
|
||||
cy.contains('Mensuel')
|
||||
cy.contains('Annuel')
|
||||
cy.contains('Montant mensuel')
|
||||
cy.contains('Montant annuel')
|
||||
|
||||
cy.contains('Coût total')
|
||||
cy.get(coutTotalSelector).should('exist')
|
||||
|
@ -57,8 +57,8 @@ describe('Test prerender', function () {
|
|||
cy.contains('Impôt sur le revenu')
|
||||
cy.contains('Impôt sur les sociétés')
|
||||
|
||||
cy.contains('Mensuel')
|
||||
cy.contains('Annuel')
|
||||
cy.contains('Montant mensuel')
|
||||
cy.contains('Montant annuel')
|
||||
|
||||
cy.contains("Chiffre d'affaires")
|
||||
cy.get('input[id="entreprisechiffred\'affaires"]').should('exist')
|
||||
|
@ -93,7 +93,7 @@ describe('Test prerender', function () {
|
|||
|
||||
cy.contains('a', 'Employee')
|
||||
cy.contains('a', 'Auto-entrepreneur')
|
||||
cy.contains('a', 'Liberal profession')
|
||||
cy.contains('a', 'Status Comparison')
|
||||
cy.contains('a', 'Discover all the simulators and assistants')
|
||||
},
|
||||
},
|
||||
|
|
|
@ -20,7 +20,7 @@ describe('Simulateur auto-entrepreneur', { testIsolation: 'off' }, function () {
|
|||
})
|
||||
|
||||
it('should not have negative value', function () {
|
||||
cy.contains('Mensuel').click()
|
||||
cy.contains('Montant mensuel').click()
|
||||
cy.get(inputSelector).first().type('{selectall}5000')
|
||||
cy.get(inputSelector).each(($input) => {
|
||||
cy.wrap($input).should(($i) => {
|
||||
|
|
|
@ -17,7 +17,7 @@ export const runSimulateurTest = (simulateur) => {
|
|||
})
|
||||
|
||||
it('should display a result when entering a value in any of the currency input', function () {
|
||||
cy.contains(fr ? 'Annuel' : 'Yearly').click()
|
||||
cy.contains(fr ? 'Montant annuel' : 'Yearly amount').click()
|
||||
if (['indépendant', 'profession-liberale'].includes(simulateur)) {
|
||||
cy.get(chargeInputSelector).type(1000)
|
||||
}
|
||||
|
@ -40,13 +40,13 @@ export const runSimulateurTest = (simulateur) => {
|
|||
})
|
||||
|
||||
it('should allow to change period', function () {
|
||||
cy.contains(fr ? 'Annuel' : 'Yearly').click()
|
||||
cy.contains(fr ? 'Montant annuel' : 'Yearly amount').click()
|
||||
cy.get(inputSelector).first().type('{selectall}12000')
|
||||
if (['indépendant', 'profession-liberale'].includes(simulateur)) {
|
||||
cy.get(chargeInputSelector).type('{selectall}6000')
|
||||
}
|
||||
cy.get(inputSelector).eq(1).invoke('val').should('not.be.empty')
|
||||
cy.contains(fr ? 'Mensuel' : 'Monthly').click()
|
||||
cy.contains(fr ? 'Montant mensuel' : 'Monthly amount').click()
|
||||
cy.get(inputSelector)
|
||||
.first()
|
||||
.invoke('val')
|
||||
|
@ -54,7 +54,7 @@ export const runSimulateurTest = (simulateur) => {
|
|||
if (['indépendant', 'profession-liberale'].includes(simulateur)) {
|
||||
cy.get(chargeInputSelector).first().invoke('val').should('match', /500/)
|
||||
}
|
||||
cy.contains(fr ? 'Annuel' : 'Yearly').click()
|
||||
cy.contains(fr ? 'Montant annuel' : 'Yearly amount').click()
|
||||
})
|
||||
|
||||
it('should allow to navigate to a documentation page', function () {
|
||||
|
|
|
@ -97,6 +97,7 @@
|
|||
"react-router-dom": "^6.4.4",
|
||||
"react-signature-pad-wrapper": "^3.3.1",
|
||||
"react-spring": "^9.5.5",
|
||||
"react-tooltip": "^5.4.0",
|
||||
"react-use-measure": "^2.1.1",
|
||||
"recharts": "2.3.2",
|
||||
"reduce-reducers": "^1.0.4",
|
||||
|
|
|
@ -109,10 +109,16 @@ const StyledValue = styled.span<{ $flashOnChange: boolean }>`
|
|||
type ConditionProps = {
|
||||
expression: PublicodesExpression | ASTNode
|
||||
children: React.ReactNode
|
||||
engine?: Engine<DottedName>
|
||||
}
|
||||
|
||||
export function Condition({ expression, children }: ConditionProps) {
|
||||
const engine = useEngine()
|
||||
export function Condition({
|
||||
expression,
|
||||
children,
|
||||
engine: engineFromProps,
|
||||
}: ConditionProps) {
|
||||
const defaultEngine = useEngine()
|
||||
const engine = engineFromProps ?? defaultEngine
|
||||
const nodeValue = engine.evaluate({ '!=': [expression, 'non'] }).nodeValue
|
||||
|
||||
if (!nodeValue) {
|
||||
|
@ -122,15 +128,39 @@ export function Condition({ expression, children }: ConditionProps) {
|
|||
return <>{children}</>
|
||||
}
|
||||
|
||||
export function WhenValueEquals({
|
||||
expression,
|
||||
value,
|
||||
children,
|
||||
engine: engineFromProps,
|
||||
}: ConditionProps & { value: string | number }) {
|
||||
const defaultEngine = useEngine()
|
||||
const engine = engineFromProps ?? defaultEngine
|
||||
const nodeValue = engine.evaluate(expression).nodeValue
|
||||
|
||||
if (nodeValue !== value) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <>{children}</>
|
||||
}
|
||||
|
||||
export function WhenApplicable({
|
||||
dottedName,
|
||||
children,
|
||||
engine,
|
||||
}: {
|
||||
dottedName: DottedName
|
||||
children: React.ReactNode
|
||||
engine?: Engine<DottedName>
|
||||
}) {
|
||||
const engine = useEngine()
|
||||
if (engine.evaluate({ 'est applicable': dottedName }).nodeValue !== true) {
|
||||
const defaultEngine = useEngine()
|
||||
|
||||
const engineValue = engine ?? defaultEngine
|
||||
|
||||
if (
|
||||
engineValue.evaluate({ 'est applicable': dottedName }).nodeValue !== true
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
@ -140,13 +170,19 @@ export function WhenApplicable({
|
|||
export function WhenNotApplicable({
|
||||
dottedName,
|
||||
children,
|
||||
engine,
|
||||
}: {
|
||||
dottedName: DottedName
|
||||
children: React.ReactNode
|
||||
engine?: Engine<DottedName>
|
||||
}) {
|
||||
const engine = useEngine()
|
||||
const defaultEngine = useEngine()
|
||||
|
||||
const engineValue = engine ?? defaultEngine
|
||||
|
||||
if (
|
||||
engine.evaluate({ 'est non applicable': dottedName }).nodeValue !== true
|
||||
engineValue.evaluate({ 'est non applicable': dottedName }).nodeValue !==
|
||||
true
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
@ -157,12 +193,17 @@ export function WhenNotApplicable({
|
|||
export function WhenAlreadyDefined({
|
||||
dottedName,
|
||||
children,
|
||||
engine,
|
||||
}: {
|
||||
dottedName: DottedName
|
||||
children: React.ReactNode
|
||||
engine?: Engine<DottedName>
|
||||
}) {
|
||||
const engine = useEngine()
|
||||
if (engine.evaluate({ 'est non défini': dottedName }).nodeValue) {
|
||||
const defaultEngine = useEngine()
|
||||
|
||||
const engineValue = engine ?? defaultEngine
|
||||
|
||||
if (engineValue.evaluate({ 'est non défini': dottedName }).nodeValue) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
|
|
@ -12,11 +12,11 @@ export default function PeriodSwitch() {
|
|||
const { t } = useTranslation()
|
||||
const periods = [
|
||||
{
|
||||
label: t('Mensuel'),
|
||||
label: t('Montant mensuel'),
|
||||
unit: '€/mois',
|
||||
},
|
||||
{
|
||||
label: t('Annuel'),
|
||||
label: t('Montant annuel'),
|
||||
unit: '€/an',
|
||||
},
|
||||
]
|
||||
|
@ -26,6 +26,8 @@ export default function PeriodSwitch() {
|
|||
<ToggleGroup
|
||||
value={currentUnit}
|
||||
onChange={(unit: string) => dispatch(updateUnit(unit))}
|
||||
mode="tab"
|
||||
hideRadio
|
||||
>
|
||||
{periods.map(({ label, unit }) => (
|
||||
<span
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { DottedName } from 'modele-social'
|
||||
import Engine from 'publicodes'
|
||||
import { RuleLink as EngineRuleLink } from 'publicodes-react'
|
||||
import React, { useContext } from 'react'
|
||||
import React, { ReactNode, useContext } from 'react'
|
||||
|
||||
import { Link } from '@/design-system/typography/link'
|
||||
import { useSitePaths } from '@/sitePaths'
|
||||
|
@ -14,13 +15,17 @@ export default function RuleLink(
|
|||
displayIcon?: boolean
|
||||
children?: React.ReactNode
|
||||
documentationPath?: string
|
||||
linkComponent?: ReactNode
|
||||
engine?: Engine<DottedName>
|
||||
} & Omit<React.ComponentProps<typeof Link>, 'to' | 'children'>
|
||||
) {
|
||||
const { absoluteSitePaths } = useSitePaths()
|
||||
const engine = useContext(EngineContext)
|
||||
const defaultEngine = useContext(EngineContext)
|
||||
|
||||
const engineUsed = props?.engine ?? defaultEngine
|
||||
|
||||
try {
|
||||
engine.getRule(props.dottedName)
|
||||
engineUsed.getRule(props.dottedName)
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error)
|
||||
|
@ -32,8 +37,8 @@ export default function RuleLink(
|
|||
<EngineRuleLink
|
||||
{...props}
|
||||
// @ts-ignore
|
||||
linkComponent={Link}
|
||||
engine={engine}
|
||||
linkComponent={props?.linkComponent || Link}
|
||||
engine={engineUsed}
|
||||
documentationPath={
|
||||
props.documentationPath ?? absoluteSitePaths.documentation.index
|
||||
}
|
||||
|
|
|
@ -15,14 +15,11 @@ const QuestionsContainer = styled.div`
|
|||
padding: ${({ theme }) => ` ${theme.spacings.xs} ${theme.spacings.lg}`};
|
||||
border-radius: ${({ theme }) =>
|
||||
`0 0 ${theme.box.borderRadius} ${theme.box.borderRadius}`};
|
||||
background: ${({ theme }) => {
|
||||
const palettePrimary = theme.colors.bases.primary
|
||||
const paletteGrey = theme.colors.extended.grey
|
||||
|
||||
return theme.darkMode
|
||||
? `linear-gradient(60deg, ${paletteGrey[800]} 0%, ${paletteGrey[700]} 100%);`
|
||||
: `linear-gradient(60deg, ${palettePrimary[200]} 0%, ${palettePrimary[100]} 100%);`
|
||||
}};
|
||||
background-color: ${({ theme }) =>
|
||||
theme.darkMode
|
||||
? theme.colors.extended.grey[700]
|
||||
: theme.colors.extended.grey[100]};
|
||||
box-shadow: ${({ theme }) => theme.elevations[2]};
|
||||
`
|
||||
|
||||
const Notice = styled(Body)`
|
||||
|
|
|
@ -5,11 +5,14 @@ import { useDispatch, useSelector } from 'react-redux'
|
|||
import styled from 'styled-components'
|
||||
|
||||
import { updateSituation } from '@/actions/actions'
|
||||
import { ForceThemeProvider } from '@/contexts/DarkModeContext'
|
||||
import { Grid } from '@/design-system/layout'
|
||||
import { Strong } from '@/design-system/typography'
|
||||
import { Body, SmallBody } from '@/design-system/typography/paragraphs'
|
||||
import { targetUnitSelector } from '@/selectors/simulationSelectors'
|
||||
|
||||
import RuleLink from '../RuleLink'
|
||||
import { ExplicableRule } from '../conversation/Explicable'
|
||||
import RuleInput, { InputProps } from '../conversation/RuleInput'
|
||||
import AnimatedTargetValue from '../ui/AnimatedTargetValue'
|
||||
import { Appear } from '../ui/animate'
|
||||
|
@ -23,6 +26,7 @@ type SimulationGoalProps = {
|
|||
appear?: boolean
|
||||
editable?: boolean
|
||||
isTypeBoolean?: boolean
|
||||
isInfoMode?: boolean
|
||||
|
||||
onUpdateSituation?: (
|
||||
name: DottedName,
|
||||
|
@ -38,6 +42,7 @@ export function SimulationGoal({
|
|||
appear = true,
|
||||
editable = true,
|
||||
isTypeBoolean = false, // TODO : remove when type inference works in publicodes
|
||||
isInfoMode = false,
|
||||
}: SimulationGoalProps) {
|
||||
const dispatch = useDispatch()
|
||||
const engine = useEngine()
|
||||
|
@ -76,12 +81,34 @@ export function SimulationGoal({
|
|||
>
|
||||
<Grid item md="auto" sm={small ? 9 : 8} xs={8}>
|
||||
<StyledGoalHeader>
|
||||
<RuleLink
|
||||
id={`${dottedName.replace(/\s|\./g, '')}-label`}
|
||||
dottedName={dottedName}
|
||||
>
|
||||
{label}
|
||||
</RuleLink>
|
||||
{isInfoMode ? (
|
||||
<Grid
|
||||
container
|
||||
css={`
|
||||
align-items: center;
|
||||
`}
|
||||
>
|
||||
<Grid item>
|
||||
<StyledBody
|
||||
id={`${dottedName.replace(/\s|\./g, '')}-label`}
|
||||
>
|
||||
<Strong>{label || rule.title}</Strong>
|
||||
</StyledBody>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<ForceThemeProvider forceTheme="default">
|
||||
<ExplicableRule dottedName={dottedName} light />
|
||||
</ForceThemeProvider>
|
||||
</Grid>
|
||||
</Grid>
|
||||
) : (
|
||||
<RuleLink
|
||||
id={`${dottedName.replace(/\s|\./g, '')}-label`}
|
||||
dottedName={dottedName}
|
||||
>
|
||||
{label}
|
||||
</RuleLink>
|
||||
)}
|
||||
|
||||
{rule.rawNode.résumé && (
|
||||
<StyledSmallBody
|
||||
|
@ -172,3 +199,7 @@ const StyledGoal = styled.div`
|
|||
const StyledSmallBody = styled(SmallBody)`
|
||||
margin-bottom: 0;
|
||||
`
|
||||
const StyledBody = styled(Body)`
|
||||
color: ${({ theme }) => theme.colors.extended.grey[100]};
|
||||
margin: 0;
|
||||
`
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
import styled, { css } from 'styled-components'
|
||||
|
||||
import { ForceThemeProvider } from '@/contexts/DarkModeContext'
|
||||
import { Grid } from '@/design-system/layout'
|
||||
import { Link } from '@/design-system/typography/link'
|
||||
import { Body } from '@/design-system/typography/paragraphs'
|
||||
import { firstStepCompletedSelector } from '@/selectors/simulationSelectors'
|
||||
|
||||
import { Logo } from '../Logo'
|
||||
|
@ -13,7 +14,6 @@ import { WatchInitialRender } from '../utils/useInitialRender'
|
|||
import { useIsEmbedded } from '../utils/useIsEmbedded'
|
||||
|
||||
type SimulationGoalsProps = {
|
||||
className?: string
|
||||
legend: string
|
||||
publique?:
|
||||
| 'employeur'
|
||||
|
@ -36,29 +36,37 @@ export function SimulationGoals({
|
|||
|
||||
return (
|
||||
<WatchInitialRender>
|
||||
<TopSection toggles={toggles} />
|
||||
<div role="group" aria-labelledby="simulator-legend-label">
|
||||
<TopSection toggles={toggles} />
|
||||
|
||||
<StyledSimulationGoals
|
||||
isEmbeded={isEmbeded}
|
||||
isFirstStepCompleted={isFirstStepCompleted}
|
||||
publique={publique}
|
||||
role="group"
|
||||
id="simulator-legend"
|
||||
aria-labelledby="simulator-legend-label"
|
||||
aria-live="polite"
|
||||
>
|
||||
<ForceThemeProvider forceTheme="dark">
|
||||
<div className="sr-only" aria-hidden id="simulator-legend-label">
|
||||
{legend}
|
||||
</div>
|
||||
{children}
|
||||
</ForceThemeProvider>
|
||||
</StyledSimulationGoals>
|
||||
<SimulationGoalsContainer
|
||||
isEmbeded={isEmbeded}
|
||||
isFirstStepCompleted={isFirstStepCompleted}
|
||||
publique={publique}
|
||||
id="simulator-legend"
|
||||
aria-live="polite"
|
||||
>
|
||||
<ForceThemeProvider forceTheme="dark">
|
||||
<div className="sr-only" aria-hidden id="simulator-legend-label">
|
||||
{legend}
|
||||
</div>
|
||||
<Body className="visually-hidden">
|
||||
<em>
|
||||
<Trans>
|
||||
Les données de simulations se mettront automatiquement à jour
|
||||
après la modification d'un champ.
|
||||
</Trans>
|
||||
</em>
|
||||
</Body>
|
||||
{children}
|
||||
</ForceThemeProvider>
|
||||
</SimulationGoalsContainer>
|
||||
</div>
|
||||
</WatchInitialRender>
|
||||
)
|
||||
}
|
||||
|
||||
const StyledSimulationGoals = styled.div<
|
||||
export const SimulationGoalsContainer = styled.div<
|
||||
Pick<SimulationGoalsProps, 'publique'> & {
|
||||
isFirstStepCompleted: boolean
|
||||
isEmbeded: boolean
|
||||
|
@ -67,7 +75,10 @@ const StyledSimulationGoals = styled.div<
|
|||
z-index: 1;
|
||||
position: relative;
|
||||
padding: ${({ theme }) => `${theme.spacings.sm} ${theme.spacings.lg}`};
|
||||
border-radius: ${({ theme }) => theme.box.borderRadius};
|
||||
border-start-end-radius: 0;
|
||||
border-end-start-radius: 0;
|
||||
border-end-end-radius: 0;
|
||||
border-start-start-radius: ${({ theme }) => theme.box.borderRadius};
|
||||
${({ isFirstStepCompleted }) =>
|
||||
isFirstStepCompleted &&
|
||||
css`
|
||||
|
@ -81,13 +92,19 @@ const StyledSimulationGoals = styled.div<
|
|||
? theme.colors.publics[publique]
|
||||
: theme.colors.bases.primary
|
||||
|
||||
return css`linear-gradient(60deg, ${colorPalette[800]} 0%, ${colorPalette[600]} 100%);`
|
||||
return css`
|
||||
${colorPalette[600]};
|
||||
`
|
||||
}};
|
||||
|
||||
@media print {
|
||||
background: initial;
|
||||
padding: 0;
|
||||
}
|
||||
@media (max-width: ${({ theme }) => theme.breakpointsWidth.sm}) {
|
||||
border-start-start-radius: ${({ theme }) => theme.box.borderRadius};
|
||||
border-start-end-radius: ${({ theme }) => theme.box.borderRadius};
|
||||
}
|
||||
`
|
||||
|
||||
function TopSection({ toggles }: { toggles?: React.ReactNode }) {
|
||||
|
@ -129,9 +146,9 @@ const Section = styled(Grid).attrs({ container: true })`
|
|||
gap: ${({ theme }) => theme.spacings.xs};
|
||||
`
|
||||
|
||||
const ToggleSection = styled.div`
|
||||
export const ToggleSection = styled.div`
|
||||
padding: ${({ theme }) => theme.spacings.sm} 0;
|
||||
|
||||
padding-bottom: 0;
|
||||
display: flex;
|
||||
justify-content: right;
|
||||
text-align: right;
|
||||
|
|
|
@ -10,7 +10,6 @@ import { ConversationProps } from '@/components/conversation/Conversation'
|
|||
import { PopoverWithTrigger } from '@/design-system'
|
||||
import { Grid, Spacing } from '@/design-system/layout'
|
||||
import { Link } from '@/design-system/typography/link'
|
||||
import { Body } from '@/design-system/typography/paragraphs'
|
||||
import {
|
||||
companySituationSelector,
|
||||
firstStepCompletedSelector,
|
||||
|
@ -21,7 +20,7 @@ import Banner from '../Banner'
|
|||
import AnswerList from '../conversation/AnswerList'
|
||||
import PrintExportRecover from '../simulationExplanation/PrintExportRecover'
|
||||
import PreviousSimulationBanner from './../PreviousSimulationBanner'
|
||||
import { FadeIn, FromTop } from './../ui/animate'
|
||||
import { FromTop } from './../ui/animate'
|
||||
import { Questions } from './Questions'
|
||||
|
||||
export { Questions } from './Questions'
|
||||
|
@ -37,9 +36,12 @@ type SimulationProps = {
|
|||
hideDetails?: boolean
|
||||
showQuestionsFromBeginning?: boolean
|
||||
customEndMessages?: ConversationProps['customEndMessages']
|
||||
fullWidth?: boolean
|
||||
id?: string
|
||||
}
|
||||
|
||||
const StyledGrid = styled(Grid)`
|
||||
width: 100%;
|
||||
@media print {
|
||||
max-width: initial;
|
||||
flex-basis: initial;
|
||||
|
@ -57,6 +59,8 @@ export default function Simulation({
|
|||
showQuestionsFromBeginning,
|
||||
engines,
|
||||
hideDetails = false,
|
||||
fullWidth,
|
||||
id,
|
||||
}: SimulationProps) {
|
||||
const firstStepCompleted = useSelector(firstStepCompletedSelector)
|
||||
const existingCompany = !!useSelector(companySituationSelector)[
|
||||
|
@ -74,15 +78,21 @@ export default function Simulation({
|
|||
css={`
|
||||
justify-content: center;
|
||||
`}
|
||||
id={id}
|
||||
>
|
||||
<StyledGrid item xl={9} lg={10} md={11} sm={12}>
|
||||
<StyledGrid
|
||||
item
|
||||
css={`
|
||||
${fullWidth
|
||||
? `width: 100%; max-width: none; flex-basis: auto;`
|
||||
: ''}
|
||||
`}
|
||||
xl={9}
|
||||
lg={10}
|
||||
md={11}
|
||||
sm={12}
|
||||
>
|
||||
<PrintExportRecover />
|
||||
<Body className="visually-hidden">
|
||||
<Trans>
|
||||
Les données de simulations se mettront automatiquement à jour
|
||||
après la modification d'un champ.
|
||||
</Trans>
|
||||
</Body>
|
||||
{children}
|
||||
<FromTop>
|
||||
{(firstStepCompleted || showQuestionsFromBeginning) && (
|
||||
|
|
|
@ -28,6 +28,7 @@ import {
|
|||
import { Emoji } from '@/design-system/emoji'
|
||||
import { Item, Select } from '@/design-system/field/Select'
|
||||
import { Spacing } from '@/design-system/layout'
|
||||
import { Switch } from '@/design-system/switch'
|
||||
import { H3, H4 } from '@/design-system/typography/heading'
|
||||
|
||||
import { ExplicableRule } from './Explicable'
|
||||
|
@ -166,75 +167,84 @@ function RadioChoice<Names extends string = DottedName>({
|
|||
|
||||
return (
|
||||
<>
|
||||
{choice.children.map((node) => (
|
||||
<Fragment key={node.dottedName}>
|
||||
{' '}
|
||||
{hiddenOptions.includes(
|
||||
node.dottedName as DottedName
|
||||
) ? null : 'children' in node ? (
|
||||
<div
|
||||
role="group"
|
||||
aria-labelledby={
|
||||
node.dottedName.replace(/\s|\./g, '') + '-legend'
|
||||
}
|
||||
css={`
|
||||
margin-top: -1rem;
|
||||
`}
|
||||
>
|
||||
<H4 as={H3} id={node.dottedName + '-legend'}>
|
||||
{node.title}
|
||||
</H4>
|
||||
<Spacing lg />
|
||||
<StyledSubRadioGroup>
|
||||
<RadioChoice
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus={autoFocus}
|
||||
defaultValue={defaultValue}
|
||||
choice={node}
|
||||
rootDottedName={rootDottedName}
|
||||
type={type}
|
||||
/>
|
||||
</StyledSubRadioGroup>
|
||||
</div>
|
||||
) : (
|
||||
<span>
|
||||
<Radio
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus={
|
||||
// Doit autoFocus si correspond à la valeur par défaut
|
||||
(defaultValue &&
|
||||
defaultValue ===
|
||||
`'${relativeDottedName(
|
||||
rootDottedName,
|
||||
node.dottedName
|
||||
)}'` &&
|
||||
autoFocus) ||
|
||||
// Sinon doit autoFocus automatiquement
|
||||
autoFocus
|
||||
{choice.children.map((node) => {
|
||||
return (
|
||||
<Fragment key={node.dottedName}>
|
||||
{' '}
|
||||
{hiddenOptions.includes(
|
||||
node.dottedName as DottedName
|
||||
) ? null : 'children' in node ? (
|
||||
<div
|
||||
role="group"
|
||||
aria-labelledby={
|
||||
node.dottedName.replace(/\s|\./g, '') + '-legend'
|
||||
}
|
||||
value={`'${relativeDottedName(
|
||||
rootDottedName,
|
||||
node.dottedName
|
||||
)}'`}
|
||||
id={`radio-input-${node.dottedName.replace(
|
||||
/\s|\./g,
|
||||
''
|
||||
)}-${rootDottedName.replace(/\s|\./g, '')}`}
|
||||
css={`
|
||||
margin-top: -1rem;
|
||||
`}
|
||||
>
|
||||
{node.title}{' '}
|
||||
{node.rawNode.icônes && <Emoji emoji={node.rawNode.icônes} />}
|
||||
</Radio>{' '}
|
||||
{type !== 'toggle' && (
|
||||
<ExplicableRule
|
||||
light
|
||||
dottedName={node.dottedName as DottedName}
|
||||
aria-label={`En savoir plus sur ${node.title}`}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</Fragment>
|
||||
))}
|
||||
<H4 as={H3} id={node.dottedName + '-legend'}>
|
||||
{node.title}
|
||||
</H4>
|
||||
<Spacing lg />
|
||||
<StyledSubRadioGroup>
|
||||
<RadioChoice
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus={autoFocus}
|
||||
defaultValue={defaultValue}
|
||||
choice={node}
|
||||
rootDottedName={rootDottedName}
|
||||
type={type}
|
||||
/>
|
||||
</StyledSubRadioGroup>
|
||||
</div>
|
||||
) : (
|
||||
<span>
|
||||
<Radio
|
||||
// eslint-disable-next-line jsx-a11y/no-autofocus
|
||||
autoFocus={
|
||||
// Doit autoFocus si correspond à la valeur par défaut
|
||||
(defaultValue &&
|
||||
defaultValue ===
|
||||
`'${relativeDottedName(
|
||||
rootDottedName,
|
||||
node.dottedName
|
||||
)}'` &&
|
||||
autoFocus) ||
|
||||
// Sinon doit autoFocus automatiquement
|
||||
autoFocus
|
||||
}
|
||||
value={`'${relativeDottedName(
|
||||
rootDottedName,
|
||||
node.dottedName
|
||||
)}'`}
|
||||
id={`radio-input-${relativeDottedName(
|
||||
rootDottedName,
|
||||
node.dottedName
|
||||
).replace(/\s|\./g, '')}-${rootDottedName.replace(
|
||||
/\s|\./g,
|
||||
''
|
||||
)}`}
|
||||
>
|
||||
{node.title}{' '}
|
||||
{node.rawNode.icônes && <Emoji emoji={node.rawNode.icônes} />}
|
||||
</Radio>{' '}
|
||||
{type !== 'toggle' && (
|
||||
<ExplicableRule
|
||||
light
|
||||
dottedName={node.dottedName as DottedName}
|
||||
aria-label={`En savoir plus sur ${node.title}`}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</Fragment>
|
||||
)
|
||||
})}
|
||||
{choice.canGiveUp && (
|
||||
<>
|
||||
<Radio value={'non'}>{t('Aucun')}</Radio>
|
||||
|
@ -334,3 +344,25 @@ export function useSelection<Names extends string = DottedName>({
|
|||
|
||||
return { currentSelection, handleChange, defaultValue }
|
||||
}
|
||||
|
||||
export const SwitchInput = (props: {
|
||||
onChange?: (isSelected: boolean) => void
|
||||
defaultSelected?: boolean
|
||||
label?: string
|
||||
id?: string
|
||||
key?: string
|
||||
}) => {
|
||||
const { onChange, id, label, defaultSelected, key } = props
|
||||
|
||||
return (
|
||||
<Switch
|
||||
defaultSelected={defaultSelected}
|
||||
onChange={(isSelected: boolean) => onChange && onChange(isSelected)}
|
||||
light
|
||||
id={id}
|
||||
key={key}
|
||||
>
|
||||
{label}
|
||||
</Switch>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -132,7 +132,7 @@ export default function Conversation({
|
|||
<Grid container spacing={2}>
|
||||
{previousAnswers.length > 0 && (
|
||||
<Grid item xs={6} sm="auto">
|
||||
<Button light onPress={goToPrevious} size="XS">
|
||||
<Button color="secondary" onPress={goToPrevious} size="XS">
|
||||
<span aria-hidden>←</span> <Trans>Précédent</Trans>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
@ -141,7 +141,8 @@ export default function Conversation({
|
|||
<Button
|
||||
size="XS"
|
||||
onPress={goToNextQuestion}
|
||||
light={!currentQuestionIsAnswered}
|
||||
light={!currentQuestionIsAnswered ? true : undefined}
|
||||
color={!currentQuestionIsAnswered ? 'secondary' : undefined}
|
||||
aria-label={
|
||||
currentQuestionIsAnswered
|
||||
? t('Suivant, passer à la question suivante')
|
||||
|
|
|
@ -3,20 +3,24 @@ import { useContext } from 'react'
|
|||
|
||||
import { EngineContext } from '@/components/utils/EngineContext'
|
||||
import { Markdown } from '@/components/utils/markdown'
|
||||
import HelpButton from '@/design-system/buttons/HelpButton'
|
||||
import HelpButtonWithPopover from '@/design-system/buttons/HelpButtonWithPopover'
|
||||
import { Spacing } from '@/design-system/layout'
|
||||
import { H3 } from '@/design-system/typography/heading'
|
||||
|
||||
import { References } from '../References'
|
||||
import RuleLink from '../RuleLink'
|
||||
|
||||
export function ExplicableRule<Names extends string = DottedName>({
|
||||
dottedName,
|
||||
light,
|
||||
bigPopover,
|
||||
title,
|
||||
...props
|
||||
}: {
|
||||
dottedName: Names
|
||||
light?: boolean
|
||||
bigPopover?: boolean
|
||||
title?: string
|
||||
}) {
|
||||
const engine = useContext(EngineContext)
|
||||
|
||||
|
@ -33,10 +37,10 @@ export function ExplicableRule<Names extends string = DottedName>({
|
|||
// TODO montrer les variables de type 'une possibilité'
|
||||
|
||||
return (
|
||||
<HelpButton
|
||||
<HelpButtonWithPopover
|
||||
key={rule.dottedName}
|
||||
type="info"
|
||||
title={rule.title}
|
||||
title={title ?? rule.title}
|
||||
light={light}
|
||||
bigPopover={bigPopover}
|
||||
className="print-hidden"
|
||||
|
@ -44,12 +48,18 @@ export function ExplicableRule<Names extends string = DottedName>({
|
|||
{...props}
|
||||
>
|
||||
<Markdown>{rule.rawNode.description}</Markdown>
|
||||
|
||||
<RuleLink dottedName={dottedName as DottedName}>
|
||||
Lire la documentation
|
||||
</RuleLink>
|
||||
|
||||
{rule.rawNode.références && (
|
||||
<>
|
||||
<H3>Liens utiles</H3>
|
||||
<References references={rule.rawNode.références} />
|
||||
</>
|
||||
)}
|
||||
</HelpButton>
|
||||
<Spacing xxl />
|
||||
</HelpButtonWithPopover>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -22,6 +22,8 @@ import TextInput from './TextInput'
|
|||
import SelectPaysDétachement from './select/SelectPaysDétachement'
|
||||
import SelectAtmp from './select/SelectTauxRisque'
|
||||
|
||||
type InputType = 'radio' | 'card' | 'toggle' | 'select'
|
||||
|
||||
type Props<Names extends string = DottedName> = Omit<
|
||||
React.HTMLAttributes<HTMLInputElement>,
|
||||
'onChange' | 'defaultValue' | 'onSubmit'
|
||||
|
@ -42,10 +44,11 @@ type Props<Names extends string = DottedName> = Omit<
|
|||
// cf .https://github.com/betagouv/mon-entreprise/issues/1489#issuecomment-823058710
|
||||
showDefaultDateValue?: boolean
|
||||
onSubmit?: (source?: string) => void
|
||||
inputType?: 'radio' | 'card' | 'toggle' | 'select'
|
||||
inputType?: InputType
|
||||
formatOptions?: Intl.NumberFormatOptions
|
||||
displayedUnit?: string
|
||||
modifiers?: Record<string, string>
|
||||
engine?: Engine<DottedName>
|
||||
}
|
||||
|
||||
export type InputProps<Name extends string = string> = Omit<
|
||||
|
@ -77,13 +80,16 @@ export default function RuleInput<Names extends string = DottedName>({
|
|||
missing,
|
||||
inputType,
|
||||
modifiers = {},
|
||||
engine,
|
||||
...props
|
||||
}: Props<Names>) {
|
||||
const engine = useContext(EngineContext)
|
||||
const rule = engine.getRule(dottedName)
|
||||
const evaluation = engine.evaluate({ valeur: dottedName, ...modifiers })
|
||||
const value = evaluation.nodeValue
|
||||
const defaultEngine = useContext(EngineContext)
|
||||
|
||||
const engineValue = engine ?? defaultEngine
|
||||
|
||||
const rule = engineValue.getRule(dottedName)
|
||||
const evaluation = engineValue.evaluate({ valeur: dottedName, ...modifiers })
|
||||
const value = evaluation.nodeValue
|
||||
const shouldFocusField = useShouldFocusField()
|
||||
|
||||
const commonProps: InputProps<Names> = {
|
||||
|
@ -106,7 +112,7 @@ export default function RuleInput<Names extends string = DottedName>({
|
|||
}
|
||||
const meta = getMeta<{ affichage?: string }>(rule.rawNode, {})
|
||||
|
||||
if (getVariant(engine.getRule(dottedName))) {
|
||||
if (getVariant(engineValue.getRule(dottedName))) {
|
||||
const type =
|
||||
inputType ??
|
||||
(meta.affichage &&
|
||||
|
@ -117,7 +123,7 @@ export default function RuleInput<Names extends string = DottedName>({
|
|||
return (
|
||||
<MultipleAnswerInput
|
||||
{...commonProps}
|
||||
choice={buildVariantTree(engine, dottedName)}
|
||||
choice={buildVariantTree(engineValue, dottedName)}
|
||||
type={type}
|
||||
/>
|
||||
)
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import React from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { PopoverWithTrigger } from '@/design-system'
|
||||
import { Button } from '@/design-system/buttons'
|
||||
import { FocusStyle } from '@/design-system/global-style'
|
||||
import { EditIcon } from '@/design-system/icons'
|
||||
|
||||
import Answers from './AnswerList'
|
||||
|
||||
|
@ -17,14 +20,13 @@ export default function SeeAnswersButton({
|
|||
<>
|
||||
<PopoverWithTrigger
|
||||
trigger={(buttonProps) => (
|
||||
<Button
|
||||
{...buttonProps}
|
||||
size="XS"
|
||||
color="secondary"
|
||||
aria-haspopup="dialog"
|
||||
>
|
||||
{label ?? <Trans>Modifier mes réponses</Trans>}
|
||||
</Button>
|
||||
<StyledButton {...buttonProps} aria-haspopup="dialog">
|
||||
{label ?? (
|
||||
<>
|
||||
<EditIcon /> <Trans>Modifier mes réponses</Trans>
|
||||
</>
|
||||
)}
|
||||
</StyledButton>
|
||||
)}
|
||||
>
|
||||
{(close) => <Answers onClose={close}>{children}</Answers>}
|
||||
|
@ -32,3 +34,32 @@ export default function SeeAnswersButton({
|
|||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const StyledButton = styled(Button)`
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
border: none;
|
||||
color: ${({ theme }) =>
|
||||
theme.darkMode
|
||||
? theme.colors.extended.grey[100]
|
||||
: theme.colors.bases.primary[700]};
|
||||
border-radius: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
svg {
|
||||
margin-right: ${({ theme }) => theme.spacings.xxs};
|
||||
fill: ${({ theme }) =>
|
||||
theme.darkMode
|
||||
? theme.colors.extended.grey[100]
|
||||
: theme.colors.bases.primary[700]};
|
||||
}
|
||||
&:hover {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
${FocusStyle}
|
||||
}
|
||||
`
|
||||
|
|
|
@ -2,6 +2,7 @@ import { ReactNode, createContext, useState } from 'react'
|
|||
import { ThemeProvider } from 'styled-components'
|
||||
|
||||
import { useIsEmbedded } from '@/components/utils/useIsEmbedded'
|
||||
import { useDarkMode } from '@/hooks/useDarkMode'
|
||||
import { getItem, setItem } from '@/storage/safeLocalStorage'
|
||||
|
||||
type DarkModeContextType = [boolean, (darkMode: boolean) => void]
|
||||
|
@ -47,7 +48,7 @@ export const DarkModeProvider = ({ children }: { children: ReactNode }) => {
|
|||
)
|
||||
}
|
||||
|
||||
export type ThemeType = 'light' | 'dark'
|
||||
export type ThemeType = 'default' | 'light' | 'dark'
|
||||
|
||||
export const ForceThemeProvider = ({
|
||||
children,
|
||||
|
@ -56,12 +57,16 @@ export const ForceThemeProvider = ({
|
|||
children: ReactNode
|
||||
forceTheme?: ThemeType
|
||||
}) => {
|
||||
const [darkMode] = useDarkMode()
|
||||
|
||||
return (
|
||||
<ThemeProvider
|
||||
theme={(theme) => ({
|
||||
...theme,
|
||||
darkMode:
|
||||
forceTheme === undefined ? theme.darkMode : forceTheme === 'dark',
|
||||
forceTheme === undefined || forceTheme === 'default'
|
||||
? darkMode
|
||||
: forceTheme === 'dark',
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
|
|
|
@ -2,29 +2,140 @@ import { useAccordion, useAccordionItem } from '@react-aria/accordion'
|
|||
import { TreeState, useTreeState } from '@react-stately/tree'
|
||||
import { AriaAccordionProps } from '@react-types/accordion'
|
||||
import { Node } from '@react-types/shared'
|
||||
import { useRef } from 'react'
|
||||
import { ReactNode, useEffect, useRef, useState } from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { animated, useSpring } from 'react-spring'
|
||||
import useMeasure from 'react-use-measure'
|
||||
import styled, { css } from 'styled-components'
|
||||
|
||||
import { omit } from '@/utils'
|
||||
|
||||
import { Button } from '../buttons'
|
||||
import { FocusStyle } from '../global-style'
|
||||
import { ChevronIcon } from '../icons'
|
||||
import { Grid } from '../layout'
|
||||
import chevronImg from './chevron.svg'
|
||||
|
||||
export const Accordion = <T extends object>(props: AriaAccordionProps<T>) => {
|
||||
const SAVE_STATE_LOCALSTORAGE_KEY = 'accordion-state'
|
||||
|
||||
export const Accordion = <T extends object>(
|
||||
props: AriaAccordionProps<T> & {
|
||||
variant?: 'light'
|
||||
shouldToggleAll?: boolean
|
||||
title?: ReactNode
|
||||
isFoldable?: boolean
|
||||
shouldSaveState?: boolean
|
||||
}
|
||||
) => {
|
||||
const { title, isFoldable, shouldSaveState } = props
|
||||
const state = useTreeState<T>(props)
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const { accordionProps } = useAccordion(props, state, ref)
|
||||
const [shouldOpenAll, setShouldOpenAll] = useState(false)
|
||||
const [shouldCloseAll, setShouldCloseAll] = useState(false)
|
||||
|
||||
const openAll = () => {
|
||||
setShouldOpenAll(true)
|
||||
setTimeout(() => {
|
||||
setShouldOpenAll(false)
|
||||
}, 100)
|
||||
}
|
||||
|
||||
const closeAll = () => {
|
||||
setShouldCloseAll(true)
|
||||
setTimeout(() => {
|
||||
setShouldCloseAll(false)
|
||||
})
|
||||
}
|
||||
|
||||
// State and useEffect ne fonctionnent pas ensemble
|
||||
if (shouldOpenAll) {
|
||||
const keys = state.collection.getKeys()
|
||||
for (const key of keys) {
|
||||
if (!state.expandedKeys.has(key)) {
|
||||
state.expandedKeys.add(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldCloseAll) {
|
||||
const keys = state.collection.getKeys()
|
||||
for (const key of keys) {
|
||||
if (state.expandedKeys.has(key)) {
|
||||
state.expandedKeys.delete(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const allItemsOpen = Array.from(state.collection.getKeys()).every((key) =>
|
||||
state.expandedKeys.has(key)
|
||||
)
|
||||
|
||||
// Save opening state of Accordion between pages
|
||||
if (shouldSaveState && localStorage?.getItem(SAVE_STATE_LOCALSTORAGE_KEY)) {
|
||||
const arrayExpandedKeys = JSON.parse(
|
||||
localStorage.getItem(SAVE_STATE_LOCALSTORAGE_KEY) || ''
|
||||
) as string[]
|
||||
|
||||
const keys = state.collection.getKeys()
|
||||
for (const key of keys) {
|
||||
if (arrayExpandedKeys.includes(key as string)) {
|
||||
state.expandedKeys.add(key)
|
||||
}
|
||||
}
|
||||
localStorage.removeItem(SAVE_STATE_LOCALSTORAGE_KEY)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!shouldSaveState) return
|
||||
|
||||
return () => {
|
||||
localStorage.setItem(
|
||||
SAVE_STATE_LOCALSTORAGE_KEY,
|
||||
JSON.stringify(Array.from(state.expandedKeys))
|
||||
)
|
||||
}
|
||||
}, [state])
|
||||
|
||||
return (
|
||||
<StyledAccordionGroup {...props} {...accordionProps} ref={ref}>
|
||||
{[...state.collection].map((item) => (
|
||||
<AccordionItem<T> key={item.key} item={item} state={state} />
|
||||
))}
|
||||
</StyledAccordionGroup>
|
||||
<>
|
||||
{title && (
|
||||
<StyledGrid container>
|
||||
<Grid item>{title}</Grid>
|
||||
{isFoldable && (
|
||||
<Grid item>
|
||||
<StyledFoldButton
|
||||
underline
|
||||
onClick={() => (allItemsOpen ? closeAll() : openAll())}
|
||||
>
|
||||
<StyledChevronIcon $isOpen={allItemsOpen} />
|
||||
<Trans>{allItemsOpen ? 'Tout plier' : 'Tout déplier'}</Trans>
|
||||
</StyledFoldButton>
|
||||
</Grid>
|
||||
)}
|
||||
</StyledGrid>
|
||||
)}
|
||||
<StyledAccordionGroup
|
||||
{...omit(props, 'title')}
|
||||
{...accordionProps}
|
||||
ref={ref}
|
||||
>
|
||||
{[...state.collection].map((item) => {
|
||||
return (
|
||||
<AccordionItem<T>
|
||||
key={item.key}
|
||||
item={item}
|
||||
state={state}
|
||||
$variant={props?.variant}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</StyledAccordionGroup>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const StyledAccordionGroup = styled.div`
|
||||
const StyledAccordionGroup = styled.div<{ variant?: 'light' }>`
|
||||
max-width: 100%;
|
||||
${({ theme }) =>
|
||||
css`
|
||||
|
@ -32,20 +143,26 @@ const StyledAccordionGroup = styled.div`
|
|||
border: 1px solid ${theme.colors.bases.primary[400]};
|
||||
margin-bottom: ${theme.spacings.lg};
|
||||
`}
|
||||
${({ variant }) =>
|
||||
variant === 'light' &&
|
||||
css`
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
`}
|
||||
`
|
||||
|
||||
interface AccordionItemProps<T> {
|
||||
item: Node<T>
|
||||
state: TreeState<T>
|
||||
$variant?: 'light'
|
||||
}
|
||||
|
||||
function AccordionItem<T>(props: AccordionItemProps<T>) {
|
||||
const ref = useRef<HTMLButtonElement>(null)
|
||||
const { state, item } = props
|
||||
const { state, item, $variant } = props
|
||||
const { buttonProps, regionProps } = useAccordionItem<T>(props, state, ref)
|
||||
|
||||
const isOpen = state.expandedKeys.has(item.key)
|
||||
// const isDisabled = state.disabledKeys.has(item.key)
|
||||
|
||||
const [regionRef, { height }] = useMeasure()
|
||||
const animatedStyle = useSpring({
|
||||
|
@ -58,13 +175,19 @@ function AccordionItem<T>(props: AccordionItemProps<T>) {
|
|||
return (
|
||||
<StyledAccordionItem onMouseDown={(x) => x.stopPropagation()}>
|
||||
<StyledTitle>
|
||||
<StyledButton {...buttonProps} ref={ref}>
|
||||
<StyledButton {...buttonProps} ref={ref} $variant={$variant}>
|
||||
<span>{item.props.title}</span>
|
||||
<ChevronRightMedium aria-hidden $isOpen={isOpen} alt="" />
|
||||
</StyledButton>
|
||||
</StyledTitle>
|
||||
{/* @ts-ignore: https://github.com/pmndrs/react-spring/issues/1515 */}
|
||||
<StyledContent {...regionProps} style={animatedStyle} hidden={!isOpen}>
|
||||
<StyledContent
|
||||
// @ts-ignore
|
||||
{...regionProps}
|
||||
style={animatedStyle}
|
||||
hidden={!isOpen}
|
||||
$variant={$variant}
|
||||
>
|
||||
<div ref={regionRef}>{item.props.children}</div>
|
||||
</StyledContent>
|
||||
</StyledAccordionItem>
|
||||
|
@ -81,7 +204,7 @@ const StyledAccordionItem = styled.div`
|
|||
}
|
||||
`
|
||||
|
||||
const StyledButton = styled.button`
|
||||
const StyledButton = styled.button<{ $variant?: 'light' }>`
|
||||
display: flex;
|
||||
width: 100%;
|
||||
background: none;
|
||||
|
@ -98,11 +221,25 @@ const StyledButton = styled.button`
|
|||
}
|
||||
`}
|
||||
:hover {
|
||||
text-decoration: underline;
|
||||
text-decoration: ${({ $variant }) =>
|
||||
$variant === 'light' ? 'none' : 'underline'};
|
||||
}
|
||||
:focus {
|
||||
${FocusStyle}
|
||||
}
|
||||
|
||||
${({ theme, $variant }) =>
|
||||
$variant === 'light' &&
|
||||
css`
|
||||
background-color: transparent;
|
||||
padding: 1.5rem;
|
||||
padding-left: 0;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid ${theme.colors.bases.primary[400]};
|
||||
> span {
|
||||
border-radius: 0;
|
||||
}
|
||||
`}
|
||||
`
|
||||
|
||||
interface Chevron {
|
||||
|
@ -118,11 +255,41 @@ const ChevronRightMedium = styled.img.attrs({ src: chevronImg })<Chevron>`
|
|||
`}
|
||||
`
|
||||
|
||||
const StyledContent = styled(animated.div)<{ $isOpen: boolean }>`
|
||||
const StyledContent = styled(animated.div)<{
|
||||
$isOpen: boolean
|
||||
$variant?: 'light'
|
||||
}>`
|
||||
overflow: hidden;
|
||||
> div {
|
||||
margin: ${({ theme }) => theme.spacings.lg};
|
||||
margin: ${({ theme, $variant }) =>
|
||||
$variant !== 'light' && theme.spacings.lg};
|
||||
}
|
||||
`
|
||||
|
||||
const StyledGrid = styled(Grid)`
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const StyledFoldButton = styled(Button)`
|
||||
text-decoration: none;
|
||||
background-color: transparent;
|
||||
color: ${({ theme }) =>
|
||||
theme.darkMode
|
||||
? theme.colors.extended.grey[100]
|
||||
: theme.colors.bases.primary[700]};
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledChevronIcon = styled(ChevronIcon)<{ $isOpen?: boolean }>`
|
||||
transition: transform 0.15s ease-in-out;
|
||||
transform: ${({ $isOpen }) => ($isOpen ? 'rotate(-90deg)' : 'rotate(90deg)')};
|
||||
fill: ${({ theme }) =>
|
||||
theme.darkMode
|
||||
? theme.colors.extended.grey[100]
|
||||
: theme.colors.bases.primary[700]}!important;
|
||||
`
|
||||
|
||||
Accordion.StyledTitle = StyledTitle
|
||||
|
|
|
@ -20,6 +20,7 @@ type ButtonProps = GenericButtonOrNavLinkProps & {
|
|||
role?: string
|
||||
['aria-disabled']?: boolean
|
||||
lang?: string
|
||||
underline?: boolean
|
||||
}
|
||||
|
||||
export const Button = forwardRef(function Button(
|
||||
|
@ -27,6 +28,7 @@ export const Button = forwardRef(function Button(
|
|||
size = 'MD',
|
||||
light = false,
|
||||
color = 'primary' as const,
|
||||
underline,
|
||||
isDisabled,
|
||||
role,
|
||||
lang,
|
||||
|
@ -41,11 +43,13 @@ export const Button = forwardRef(function Button(
|
|||
|
||||
return (
|
||||
<StyledButton
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...buttonOrLinkProps}
|
||||
$size={size}
|
||||
$light={light}
|
||||
$color={color}
|
||||
$isDisabled={isDisabled}
|
||||
$underline={underline}
|
||||
role={role}
|
||||
aria-disabled={ariaButtonProps?.['aria-disabled']}
|
||||
lang={lang}
|
||||
|
@ -58,6 +62,7 @@ type StyledButtonProps = {
|
|||
$size: Size
|
||||
$light: boolean
|
||||
$isDisabled?: boolean
|
||||
$underline?: boolean
|
||||
}
|
||||
|
||||
export const StyledButton = styled.button<StyledButtonProps>`
|
||||
|
@ -143,7 +148,7 @@ export const StyledButton = styled.button<StyledButtonProps>`
|
|||
${$color === 'secondary' &&
|
||||
css`
|
||||
border-color: ${theme.colors.bases[$color][500]};
|
||||
`}
|
||||
`};
|
||||
`}
|
||||
|
||||
@media not print {
|
||||
|
@ -207,4 +212,30 @@ export const StyledButton = styled.button<StyledButtonProps>`
|
|||
`}
|
||||
}
|
||||
}
|
||||
|
||||
${({ $underline }) =>
|
||||
$underline &&
|
||||
css`
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
border: none;
|
||||
color: ${({ theme }) => theme.colors.bases.primary[700]};
|
||||
border-radius: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-decoration: underline;
|
||||
svg {
|
||||
margin-right: ${({ theme }) => theme.spacings.xxs};
|
||||
fill: ${({ theme }) => theme.colors.bases.primary[700]};
|
||||
}
|
||||
&:hover {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
${FocusStyle}
|
||||
}
|
||||
`}
|
||||
`
|
||||
|
|
|
@ -15,7 +15,7 @@ type HelpButtonProps = {
|
|||
className?: string
|
||||
}
|
||||
|
||||
export default function HelpButton({
|
||||
export default function HelpButtonWithPopover({
|
||||
children,
|
||||
title,
|
||||
type,
|
|
@ -29,4 +29,4 @@ Secondary.args = {
|
|||
children: 'Secondary XS button',
|
||||
}
|
||||
|
||||
export { CloseButton, HelpButton } from '@/design-system/buttons'
|
||||
export { CloseButton, HelpButtonWithPopover } from '@/design-system/buttons'
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
export * from './Button'
|
||||
export { default as CloseButton } from './CloseButton'
|
||||
export { default as HelpButton } from './HelpButton'
|
||||
export { default as HelpButtonWithPopover } from './HelpButtonWithPopover'
|
||||
|
|
|
@ -125,12 +125,15 @@ const IconContainer = styled.div`
|
|||
margin-top: ${({ theme }) => theme.spacings.md};
|
||||
`
|
||||
|
||||
export const CardContainer = styled.div<{ $compact?: boolean }>`
|
||||
export const CardContainer = styled.div<{
|
||||
$compact?: boolean
|
||||
$inert?: boolean
|
||||
}>`
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
cursor: ${({ $inert }) => ($inert ? 'auto' : 'pointer')};
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background-color: ${({ theme }) =>
|
||||
|
@ -141,12 +144,14 @@ export const CardContainer = styled.div<{ $compact?: boolean }>`
|
|||
box-shadow: ${({ theme }) =>
|
||||
theme.darkMode ? theme.elevationsDarkMode[2] : theme.elevations[2]};
|
||||
&:hover {
|
||||
box-shadow: ${({ theme }) =>
|
||||
theme.darkMode ? theme.elevationsDarkMode[3] : theme.elevations[3]};
|
||||
background-color: ${({ theme }) =>
|
||||
theme.darkMode
|
||||
box-shadow: ${({ theme, $inert }) =>
|
||||
!$inert &&
|
||||
(theme.darkMode ? theme.elevationsDarkMode[3] : theme.elevations[3])};
|
||||
background-color: ${({ theme, $inert }) =>
|
||||
!$inert &&
|
||||
(theme.darkMode
|
||||
? theme.colors.extended.dark[500]
|
||||
: theme.colors.bases.primary[100]};
|
||||
: theme.colors.bases.primary[100])};
|
||||
}
|
||||
padding: ${({ theme: { spacings }, $compact = false }) =>
|
||||
$compact
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
import { ComponentMeta, ComponentStory } from '@storybook/react'
|
||||
|
||||
import { CheckList } from '@/design-system'
|
||||
|
||||
// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
|
||||
export default {
|
||||
component: CheckList,
|
||||
// More on argTypes: https://storybook.js.org/docs/react/api/argtypes
|
||||
argTypes: {
|
||||
items: [{ isChecked: 'boolean', label: 'string' }],
|
||||
},
|
||||
} as ComponentMeta<typeof CheckList>
|
||||
|
||||
export const CheckListWithData: ComponentStory<typeof CheckList> = () => (
|
||||
<CheckList
|
||||
items={[
|
||||
{ isChecked: false, label: "Tient compte de l'ACRE" },
|
||||
{
|
||||
isChecked: true,
|
||||
label: "Versement libératoire de l'impôt sur le revenu",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)
|
|
@ -0,0 +1,54 @@
|
|||
import { ReactNode } from 'react'
|
||||
import styled, { css } from 'styled-components'
|
||||
|
||||
import { CheckmarkIcon, CrossIcon } from '../icons'
|
||||
|
||||
export const CheckList = ({
|
||||
items,
|
||||
}: {
|
||||
items: { label: ReactNode; isChecked: boolean }[]
|
||||
}) => {
|
||||
return (
|
||||
<StyledUl>
|
||||
{items.map((item, index) => {
|
||||
const { isChecked, label } = item
|
||||
|
||||
return (
|
||||
<StyledLi
|
||||
$isChecked={isChecked}
|
||||
key={`checklist-item-${item.toString()}-${index}`}
|
||||
>
|
||||
{isChecked ? <CheckmarkIcon /> : <CrossIcon />}
|
||||
{label}
|
||||
</StyledLi>
|
||||
)
|
||||
})}
|
||||
</StyledUl>
|
||||
)
|
||||
}
|
||||
|
||||
const StyledUl = styled.ul`
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
`
|
||||
|
||||
const StyledLi = styled.li<{ $isChecked?: boolean }>`
|
||||
list-style: none;
|
||||
svg {
|
||||
margin-right: 0.5rem;
|
||||
flex-shrink: 0;
|
||||
${({ theme, $isChecked }) =>
|
||||
!$isChecked &&
|
||||
css`
|
||||
fill: ${theme.darkMode
|
||||
? theme.colors.extended.grey[100]
|
||||
: theme.colors.extended.grey[600]}!important;
|
||||
`}
|
||||
}
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-family: ${({ theme }) => theme.fonts.main};
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
`
|
|
@ -0,0 +1,249 @@
|
|||
import FocusTrap from 'focus-trap-react'
|
||||
import React, { ReactNode, useCallback, useEffect, useState } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { Trans } from 'react-i18next'
|
||||
import styled, { css } from 'styled-components'
|
||||
|
||||
import { Button } from '../buttons'
|
||||
import { Grid } from '../layout'
|
||||
import { CloseButton, CloseButtonContainer } from '../popover/Popover'
|
||||
|
||||
export type DrawerButtonProps = {
|
||||
onClick: () => void
|
||||
['aria-expanded']: boolean
|
||||
['aria-haspopup']:
|
||||
| boolean
|
||||
| 'dialog'
|
||||
| 'menu'
|
||||
| 'grid'
|
||||
| 'listbox'
|
||||
| 'tree'
|
||||
| 'true'
|
||||
| 'false'
|
||||
| undefined
|
||||
}
|
||||
|
||||
export const Drawer = ({
|
||||
trigger,
|
||||
children,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
confirmLabel,
|
||||
cancelLabel,
|
||||
isDismissable = true,
|
||||
}: {
|
||||
trigger: ({ onClick }: DrawerButtonProps) => ReactNode
|
||||
children: ReactNode
|
||||
confirmLabel?: string
|
||||
cancelLabel?: string
|
||||
onConfirm: () => void
|
||||
onCancel?: () => void
|
||||
isDismissable?: boolean
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [isMounted, setIsMounted] = useState(false)
|
||||
|
||||
const openDrawer = () => {
|
||||
setIsMounted(true)
|
||||
}
|
||||
|
||||
const disablePageScrolling = (shouldDisableScroll: boolean) => {
|
||||
if (shouldDisableScroll) {
|
||||
document.body.style.top = `-${window.scrollY}px`
|
||||
document.body.style.position = 'fixed'
|
||||
} else {
|
||||
const scrollY = document.body.style.top
|
||||
document.body.style.position = ''
|
||||
document.body.style.top = ''
|
||||
// Avoid scroll jump
|
||||
window.scrollTo(0, parseInt(scrollY || '0') * -1)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (isMounted) {
|
||||
setIsOpen(true)
|
||||
disablePageScrolling(true)
|
||||
}
|
||||
}, [isMounted])
|
||||
|
||||
const closeDrawer = () => {
|
||||
setIsOpen(false)
|
||||
|
||||
setTimeout(() => {
|
||||
setIsMounted(false)
|
||||
if (onCancel) {
|
||||
onCancel()
|
||||
}
|
||||
}, 500)
|
||||
}
|
||||
|
||||
const handleDeactivate = useCallback(() => {
|
||||
disablePageScrolling(false)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
{trigger({
|
||||
'aria-expanded': isOpen,
|
||||
'aria-haspopup': 'dialog',
|
||||
onClick: openDrawer,
|
||||
})}
|
||||
{isMounted &&
|
||||
ReactDOM.createPortal(
|
||||
<DrawerContainer>
|
||||
<DrawerBackground $isOpen={isOpen} />
|
||||
<FocusTrap
|
||||
focusTrapOptions={{
|
||||
clickOutsideDeactivates: true,
|
||||
onDeactivate: () => {
|
||||
closeDrawer()
|
||||
handleDeactivate()
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DrawerPanel $isOpen={isOpen} role="dialog">
|
||||
{isDismissable && (
|
||||
<StyledCloseButtonContainer>
|
||||
{/* TODO : replace with Link when in design system */}
|
||||
<CloseButton onClick={() => closeDrawer()}>
|
||||
Fermer
|
||||
<svg
|
||||
role="img"
|
||||
aria-hidden
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M6.69323 17.2996C6.30271 16.9091 6.30271 16.276 6.69323 15.8854L15.8856 6.69304C16.2761 6.30252 16.9093 6.30252 17.2998 6.69304C17.6904 7.08356 17.6904 7.71673 17.2998 8.10725L8.10744 17.2996C7.71692 17.6902 7.08375 17.6902 6.69323 17.2996Z"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M6.6635 6.69306C7.05402 6.30254 7.68719 6.30254 8.07771 6.69306L17.2701 15.8854C17.6606 16.276 17.6606 16.9091 17.2701 17.2997C16.8796 17.6902 16.2464 17.6902 15.8559 17.2997L6.6635 8.10727C6.27297 7.71675 6.27297 7.08359 6.6635 6.69306Z"
|
||||
/>
|
||||
</svg>
|
||||
</CloseButton>
|
||||
</StyledCloseButtonContainer>
|
||||
)}
|
||||
<DrawerContent>
|
||||
{React.Children.map(children, (child) => {
|
||||
if (React.isValidElement(child)) {
|
||||
return React.cloneElement(child, { closeDrawer } as {
|
||||
closeDrawer: () => void
|
||||
})
|
||||
}
|
||||
})}
|
||||
</DrawerContent>
|
||||
|
||||
{onConfirm && (
|
||||
<DrawerFooter>
|
||||
<StyledGrid container>
|
||||
<Grid item>
|
||||
<Button light onPress={() => closeDrawer()}>
|
||||
{cancelLabel ?? <Trans>Annuler</Trans>}
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Button
|
||||
onPress={() => {
|
||||
onConfirm()
|
||||
setTimeout(() => closeDrawer(), 200)
|
||||
}}
|
||||
>
|
||||
{confirmLabel ?? <Trans>Confirmer</Trans>}
|
||||
</Button>
|
||||
</Grid>
|
||||
</StyledGrid>
|
||||
</DrawerFooter>
|
||||
)}
|
||||
</DrawerPanel>
|
||||
</FocusTrap>
|
||||
</DrawerContainer>,
|
||||
document.querySelector('body') as Element
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const DrawerContainer = styled.div`
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 100;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
`
|
||||
|
||||
const DrawerBackground = styled.div<{ $isOpen?: boolean }>`
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
transition: background-color 0.4s ease-in-out;
|
||||
background-color: ${({ $isOpen }) =>
|
||||
$isOpen ? 'rgba(0, 0, 0, 0.25)' : 'transparent'};
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
`
|
||||
const DrawerPanel = styled.div<{
|
||||
$isOpen: boolean
|
||||
}>`
|
||||
width: 500px;
|
||||
max-width: 100vw;
|
||||
height: 100vh;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
background-color: ${({ theme }) => theme.colors.extended.grey[100]};
|
||||
transition: transform 0.5s ease-in-out;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
transform: translateX(100%);
|
||||
z-index: 10;
|
||||
${({ $isOpen }) =>
|
||||
$isOpen &&
|
||||
css`
|
||||
transform: translateX(0);
|
||||
`}
|
||||
`
|
||||
|
||||
const DrawerContent = styled.div`
|
||||
padding: 0 ${({ theme }) => theme.spacings.xxl};
|
||||
padding-bottom: 2rem;
|
||||
min-height: 100%;
|
||||
`
|
||||
|
||||
const DrawerFooter = styled.div`
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: ${({ theme }) => theme.colors.extended.grey[100]};
|
||||
padding: ${({ theme }) => theme.spacings.xl};
|
||||
box-shadow: 0px 1px 15px rgba(0, 0, 0, 0.15);
|
||||
z-index: 10;
|
||||
`
|
||||
|
||||
const StyledGrid = styled(Grid)`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: ${({ theme }) => theme.spacings.md};
|
||||
`
|
||||
|
||||
const StyledCloseButtonContainer = styled(CloseButtonContainer)`
|
||||
position: sticky;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: ${({ theme }) => theme.colors.extended.grey[100]};
|
||||
z-index: 10;
|
||||
`
|
|
@ -0,0 +1,51 @@
|
|||
import { ComponentMeta, ComponentStory } from '@storybook/react'
|
||||
|
||||
import { Drawer } from '@/design-system/drawer'
|
||||
|
||||
import { Button } from '../buttons'
|
||||
import { DrawerButtonProps } from './Drawer'
|
||||
|
||||
// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
|
||||
export default {
|
||||
component: Drawer,
|
||||
// More on argTypes: https://storybook.js.org/docs/react/api/argtypes
|
||||
argTypes: {
|
||||
children: {
|
||||
type: 'string',
|
||||
},
|
||||
trigger: {
|
||||
type: 'function',
|
||||
},
|
||||
isDismissable: {
|
||||
type: 'boolean',
|
||||
},
|
||||
onConfirm: {
|
||||
type: 'function',
|
||||
},
|
||||
confirmLabel: {
|
||||
type: 'string',
|
||||
},
|
||||
cancelLabel: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof Drawer>
|
||||
|
||||
// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
|
||||
const Template: ComponentStory<typeof Drawer> = (args) => <Drawer {...args} />
|
||||
|
||||
export const Basic = Template.bind({})
|
||||
// More on args: https://storybook.js.org/docs/react/writing-stories/args
|
||||
Basic.args = {
|
||||
children: <div>Coucou, je suis ouvert !</div>,
|
||||
trigger: (buttonProps: DrawerButtonProps) => (
|
||||
<Button color="primary" light {...buttonProps}>
|
||||
Ouvrir le tiroir
|
||||
</Button>
|
||||
),
|
||||
isDismissable: true,
|
||||
// eslint-disable-next-line no-console
|
||||
onConfirm: () => console.log('onConfirm appelé !'),
|
||||
confirmLabel: 'Sauvegarder',
|
||||
cancelLabel: 'Annuler',
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export { Drawer } from './Drawer'
|
|
@ -142,6 +142,7 @@ export const LabelBody = styled(Body)<{
|
|||
}>`
|
||||
margin: ${({ theme }) => theme.spacings.xs} 0px;
|
||||
margin-left: ${({ theme }) => theme.spacings.xxs};
|
||||
background-color: transparent;
|
||||
${({ $hideRadio }) =>
|
||||
$hideRadio &&
|
||||
css`
|
||||
|
|
|
@ -11,11 +11,15 @@ import {
|
|||
VisibleRadio,
|
||||
} from './Radio'
|
||||
|
||||
export type Toggle = 'toggle'
|
||||
export type Tab = 'tab'
|
||||
|
||||
type ToggleGroupProps = AriaRadioGroupProps & {
|
||||
label?: string
|
||||
hideRadio?: boolean
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
mode?: Toggle | Tab
|
||||
}
|
||||
|
||||
export function ToggleGroup(props: ToggleGroupProps) {
|
||||
|
@ -33,17 +37,52 @@ export function ToggleGroup(props: ToggleGroupProps) {
|
|||
aria-label={props['aria-label'] ?? undefined}
|
||||
>
|
||||
{label && <span {...labelProps}>{label}</span>}
|
||||
<ToggleGroupContainer hideRadio={props.hideRadio ?? false}>
|
||||
<ToggleGroupContainer
|
||||
hideRadio={props.hideRadio ?? false}
|
||||
mode={props?.mode}
|
||||
>
|
||||
<RadioContext.Provider value={state}>{children}</RadioContext.Provider>
|
||||
</ToggleGroupContainer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const ToggleGroupContainer = styled.div<{ hideRadio: boolean }>`
|
||||
const TabModeStyle = css`
|
||||
border: none !important;
|
||||
border-radius: ${({ theme }) =>
|
||||
`${theme.spacings.md} ${theme.spacings.md} 0 0`}!important;
|
||||
background-color: ${({ theme }) =>
|
||||
theme.darkMode
|
||||
? theme.colors.extended.dark[600]
|
||||
: theme.colors.bases.primary[200]};
|
||||
padding: 0.875rem 2rem;
|
||||
@media (max-width: ${({ theme }) => theme.breakpointsWidth.sm}) {
|
||||
padding: 0.875rem 0.875rem;
|
||||
}
|
||||
`
|
||||
|
||||
const TabModeCheckedStyle = css`
|
||||
z-index: 2;
|
||||
border: none !important;
|
||||
background-color: ${({ theme }) => theme.colors.bases.primary[600]};
|
||||
|
||||
${LabelBody} {
|
||||
color: ${({ theme }) => theme.colors.extended.grey[100]}!important;
|
||||
}
|
||||
`
|
||||
|
||||
export const ToggleGroupContainer = styled.div<{
|
||||
hideRadio: boolean
|
||||
mode?: Toggle | Tab
|
||||
}>`
|
||||
--radius: 0.25rem;
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
${({ mode }) =>
|
||||
mode === 'tab' &&
|
||||
css`
|
||||
flex-wrap: nowrap;
|
||||
`}
|
||||
|
||||
${VisibleRadio} {
|
||||
position: relative;
|
||||
|
@ -63,6 +102,8 @@ export const ToggleGroupContainer = styled.div<{ hideRadio: boolean }>`
|
|||
theme.darkMode
|
||||
? theme.colors.extended.dark[600]
|
||||
: theme.colors.extended.grey[100]};
|
||||
|
||||
${({ mode }) => mode === 'tab' && TabModeStyle}
|
||||
}
|
||||
|
||||
${LabelBody} {
|
||||
|
@ -92,6 +133,7 @@ export const ToggleGroupContainer = styled.div<{ hideRadio: boolean }>`
|
|||
theme.darkMode
|
||||
? theme.colors.bases.primary[500]
|
||||
: theme.colors.bases.primary[200]};
|
||||
${({ mode }) => mode === 'tab' && TabModeCheckedStyle}
|
||||
}
|
||||
|
||||
${VisibleRadio}:hover {
|
||||
|
|
|
@ -15,4 +15,8 @@ export {
|
|||
ReturnIcon,
|
||||
SearchIcon,
|
||||
SuccessIcon,
|
||||
EditIcon,
|
||||
HexagonIcon,
|
||||
TriangleIcon,
|
||||
CircleIcon,
|
||||
} from '@/design-system/icons'
|
||||
|
|
|
@ -151,3 +151,257 @@ export const ReturnIcon = (props: HTMLAttributes<SVGElement>) => (
|
|||
/>
|
||||
</SvgIcon>
|
||||
)
|
||||
|
||||
export const EditIcon = (props: HTMLAttributes<SVGElement>) => (
|
||||
<svg
|
||||
{...props}
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="#1D458C"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden
|
||||
role="img"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M17.6713 4.34045C17.1223 3.79142 16.1035 3.9201 15.3957 4.62786L14.0118 6.01178L17.9882 9.98822L19.3721 8.6043C20.0799 7.89653 20.2086 6.8777 19.6595 6.32867L17.6713 4.34045ZM16.4882 11.4882L12.5118 7.51178L5.26693 14.7566C4.94924 15.0743 4.73441 15.4722 4.66412 15.8732L4.00634 19.625C3.96438 19.8643 4.13568 20.0356 4.37496 19.9937L8.12685 19.3359C8.52777 19.2656 8.92567 19.0508 9.24336 18.7331L16.4882 11.4882Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const HexagonIcon = (props: HTMLAttributes<SVGElement>) => (
|
||||
<svg
|
||||
{...props}
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="#1D458C"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden
|
||||
role="img"
|
||||
>
|
||||
<path d="M8 3L12.3301 5.5V10.5L8 13L3.66987 10.5V5.5L8 3Z" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const TriangleIcon = (props: HTMLAttributes<SVGElement>) => (
|
||||
<svg
|
||||
{...props}
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="#1D458C"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden
|
||||
role="img"
|
||||
>
|
||||
<path d="M8 3L13.1962 12H2.80385L8 3Z" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const CircleIcon = (props: HTMLAttributes<SVGElement>) => (
|
||||
<svg
|
||||
{...props}
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="#1D458C"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden
|
||||
role="img"
|
||||
>
|
||||
<path d="M8 3L8.52264 3.02739L9.03956 3.10926L9.54508 3.24472L10.0337 3.43227L10.5 3.66987L10.9389 3.95492L11.3457 4.28428L11.7157 4.65435L12.0451 5.06107L12.3301 5.5L12.5677 5.96632L12.7553 6.45492L12.8907 6.96044L12.9726 7.47736L13 8L12.9726 8.52264L12.8907 9.03956L12.7553 9.54508L12.5677 10.0337L12.3301 10.5L12.0451 10.9389L11.7157 11.3457L11.3457 11.7157L10.9389 12.0451L10.5 12.3301L10.0337 12.5677L9.54508 12.7553L9.03956 12.8907L8.52264 12.9726L8 13L7.47736 12.9726L6.96044 12.8907L6.45492 12.7553L5.96632 12.5677L5.5 12.3301L5.06107 12.0451L4.65435 11.7157L4.28428 11.3457L3.95492 10.9389L3.66987 10.5L3.43227 10.0337L3.24472 9.54508L3.10926 9.03956L3.02739 8.52264L3 8L3.02739 7.47736L3.10926 6.96044L3.24472 6.45492L3.43227 5.96632L3.66987 5.5L3.95492 5.06107L4.28428 4.65435L4.65435 4.28428L5.06107 3.95492L5.5 3.66987L5.96632 3.43227L6.45492 3.24472L6.96044 3.10926L7.47736 3.02739L8 3Z" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const HelpIcon = (props: HTMLAttributes<SVGElement>) => (
|
||||
<svg
|
||||
{...props}
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="#1D458C"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden
|
||||
role="img"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M22 12C22 17.5228 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12ZM12.9725 16.5886C12.9725 17.1472 12.5197 17.6 11.9612 17.6C11.4026 17.6 10.9498 17.1472 10.9498 16.5886C10.9498 16.0301 11.4026 15.5772 11.9612 15.5772C12.5197 15.5772 12.9725 16.0301 12.9725 16.5886ZM10.6146 9.25871C10.6784 9.05633 10.7844 8.86166 10.9534 8.71892C11.1062 8.58978 11.3903 8.43428 11.949 8.43296L11.9495 8.43297C11.9552 8.43316 11.9681 8.43369 11.9873 8.43506C12.0261 8.43782 12.088 8.44382 12.165 8.4566C12.3237 8.48295 12.5199 8.53377 12.7037 8.62531C12.8836 8.71493 13.0322 8.83327 13.138 8.9913C13.2394 9.14281 13.3416 9.38993 13.3416 9.80899C13.3416 10.1472 13.1563 10.4615 12.8509 10.747C12.7055 10.8829 12.5563 10.989 12.4428 11.0609C12.387 11.0962 12.3424 11.1216 12.3147 11.1368L12.3005 11.1444C11.704 11.4042 11.3207 11.8034 11.1131 12.2523C10.9193 12.6712 10.9196 13.0527 10.9198 13.1824L10.9198 13.1912V13.5544C10.9198 14.113 11.3726 14.5658 11.9312 14.5658C12.4897 14.5658 12.9426 14.113 12.9426 13.5544V13.1912C12.9426 13.115 12.9473 13.1049 12.9489 13.1017L12.949 13.1014L12.9492 13.101L12.9502 13.0995C12.9512 13.0981 12.9547 13.0933 12.9627 13.0858C12.9775 13.0718 13.0214 13.0352 13.1209 12.9935C13.1372 12.9867 13.1534 12.9794 13.1693 12.9717L12.7301 12.0607C13.1693 12.9717 13.1696 12.9716 13.17 12.9714L13.1707 12.9711L13.1722 12.9703L13.1758 12.9686L13.1847 12.9642L13.2095 12.9517C13.229 12.9417 13.2545 12.9283 13.285 12.9116C13.3461 12.8782 13.4283 12.831 13.5246 12.7701C13.7154 12.6493 13.9719 12.468 14.2322 12.2247C14.7383 11.7516 15.3644 10.9401 15.3644 9.80899C15.3644 9.03433 15.167 8.38616 14.819 7.8662C14.4754 7.35277 14.0247 7.02346 13.6054 6.81465C13.19 6.60776 12.7869 6.50937 12.4963 6.46111C12.3485 6.43659 12.2232 6.42394 12.1309 6.41737C12.0846 6.41407 12.046 6.41227 12.0166 6.41129C12.0019 6.4108 11.9894 6.41052 11.9793 6.41036L11.9659 6.41019L11.9605 6.41016L11.9582 6.41016L11.9571 6.41015C11.9566 6.41015 11.9561 6.41015 11.9561 7.42155V6.41015C10.9764 6.41015 10.2108 6.69811 9.64782 7.17384C9.10024 7.63652 8.82473 8.20846 8.68535 8.65082C8.51749 9.18358 8.8133 9.75155 9.34606 9.91941C9.87882 10.0873 10.4468 9.79147 10.6146 9.25871ZM12.2868 11.1516L12.2872 11.1513C12.2851 11.1524 12.2843 11.1528 12.2849 11.1525L12.2868 11.1516Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const CheckmarkIcon = (props: HTMLAttributes<SVGElement>) => (
|
||||
<svg
|
||||
{...props}
|
||||
width="24"
|
||||
height="12"
|
||||
viewBox="0 0 16 12"
|
||||
fill="#3CB053"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden
|
||||
role="img"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M15.7109 0.697112C16.0993 1.08974 16.0959 1.7229 15.7033 2.11131L6.39385 11.3207C6.00272 11.7077 5.37249 11.706 4.98346 11.3169L0.292893 6.62637C-0.0976311 6.23584 -0.0976311 5.60268 0.292893 5.21215C0.683417 4.82163 1.31658 4.82163 1.70711 5.21215L5.69437 9.19942L14.2967 0.689476C14.6894 0.301066 15.3225 0.304485 15.7109 0.697112Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const CrossIcon = (props: HTMLAttributes<SVGElement>) => (
|
||||
<svg
|
||||
{...props}
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="#6C757D"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden
|
||||
role="img"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M6.69372 17.2999C6.30319 16.9094 6.30319 16.2762 6.69372 15.8857L15.8861 6.69328C16.2766 6.30276 16.9098 6.30276 17.3003 6.69328C17.6908 7.08381 17.6908 7.71697 17.3003 8.1075L8.10793 17.2999C7.71741 17.6904 7.08424 17.6904 6.69372 17.2999Z"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M6.66399 6.69331C7.05451 6.30278 7.68768 6.30278 8.0782 6.69331L17.2706 15.8857C17.6611 16.2762 17.6611 16.9094 17.2706 17.2999C16.8801 17.6904 16.2469 17.6904 15.8564 17.2999L6.66399 8.10752C6.27346 7.71699 6.27346 7.08383 6.66399 6.69331Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const ExternalLinkIcon = (props: HTMLAttributes<SVGElement>) => (
|
||||
<svg
|
||||
{...props}
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 14 14"
|
||||
fill="#6C757D"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden
|
||||
role="img"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M6.69372 17.2999C6.30319 16.9094 6.30319 16.2762 6.69372 15.8857L15.8861 6.69328C16.2766 6.30276 16.9098 6.30276 17.3003 6.69328C17.6908 7.08381 17.6908 7.71697 17.3003 8.1075L8.10793 17.2999C7.71741 17.6904 7.08424 17.6904 6.69372 17.2999Z"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M0.292969 2.83301C0.292969 1.45229 1.41226 0.333008 2.79297 0.333008H4.6388C5.09904 0.333008 5.47214 0.706104 5.47214 1.16634C5.47214 1.62658 5.09904 1.99967 4.6388 1.99967H2.79297C2.33273 1.99967 1.95964 2.37277 1.95964 2.83301V11.208C1.95964 11.6682 2.33273 12.0413 2.79297 12.0413L11.168 12.0413C11.6282 12.0413 12.0013 11.6682 12.0013 11.208V9.36217C12.0013 8.90194 12.3744 8.52884 12.8346 8.52884C13.2949 8.52884 13.668 8.90194 13.668 9.36217V11.208C13.668 12.5887 12.5487 13.708 11.168 13.708L2.79297 13.708C1.41226 13.708 0.292969 12.5887 0.292969 11.208V2.83301ZM7.83464 1.99967C7.3744 1.99967 7.0013 1.62658 7.0013 1.16634C7.0013 0.706104 7.3744 0.333008 7.83464 0.333008H12.8346C13.2949 0.333008 13.668 0.706104 13.668 1.16634V6.16634C13.668 6.62658 13.2949 6.99967 12.8346 6.99967C12.3744 6.99967 12.0013 6.62658 12.0013 6.16634V3.17819L8.42389 6.7556C8.09845 7.08103 7.57082 7.08103 7.24538 6.7556C6.91994 6.43016 6.91994 5.90252 7.24538 5.57709L10.8228 1.99967H7.83464Z"
|
||||
fill="#212529"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const CircledArrowIcon = (props: HTMLAttributes<SVGElement>) => (
|
||||
<svg
|
||||
{...props}
|
||||
width="40"
|
||||
height="40"
|
||||
viewBox="0 0 40 40"
|
||||
fill="#1D458C"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden
|
||||
role="img"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M20 40C31.0457 40 40 31.0457 40 20C40 8.95431 31.0457 0 20 0C8.95431 0 0 8.95431 0 20C0 31.0457 8.95431 40 20 40Z"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M23.1195 13.6676C23.5243 13.2918 24.157 13.3152 24.5328 13.7199L29.7328 19.3199C30.0891 19.7036 30.0891 20.2972 29.7328 20.6808L24.5328 26.2808C24.157 26.6856 23.5243 26.709 23.1195 26.3332C22.7148 25.9574 22.6914 25.3247 23.0672 24.9199L27.6354 20.0004L23.0672 15.0808C22.6914 14.6761 22.7148 14.0434 23.1195 13.6676Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M10 20.0004C10 19.4481 10.4477 19.0004 11 19.0004L29 19.0004C29.5523 19.0004 30 19.4481 30 20.0004C30 20.5527 29.5523 21.0004 29 21.0004L11 21.0004C10.4477 21.0004 10 20.5527 10 20.0004Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const PlusCircleIcon = (props: HTMLAttributes<SVGElement>) => (
|
||||
<svg
|
||||
{...props}
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="#2E5FB6"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden
|
||||
role="img"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20ZM22 12C22 17.5229 17.5228 22 12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5229 2 22 6.47715 22 12Z"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M17.2 12C17.2 12.5523 16.7523 13 16.2 13L7.8 13C7.24771 13 6.8 12.5523 6.8 12C6.8 11.4477 7.24771 11 7.8 11L16.2 11C16.7523 11 17.2 11.4477 17.2 12Z"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M12 17.2C11.4477 17.2 11 16.7523 11 16.2L11 7.8C11 7.24772 11.4477 6.8 12 6.8C12.5523 6.8 13 7.24771 13 7.8L13 16.2C13 16.7523 12.5523 17.2 12 17.2Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const ArrowRightIcon = (props: HTMLAttributes<SVGElement>) => (
|
||||
<svg
|
||||
{...props}
|
||||
width="14"
|
||||
height="14"
|
||||
viewBox="0 0 14 14"
|
||||
fill="#212529"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden
|
||||
role="img"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M6.91877 0.667113C7.32348 0.29131 7.95621 0.314745 8.33201 0.719455L13.532 6.31945C13.8883 6.70314 13.8883 7.29668 13.532 7.68036L8.33201 13.2804C7.95621 13.6851 7.32348 13.7085 6.91877 13.3327C6.51406 12.9569 6.49062 12.3242 6.86643 11.9195L11.4346 6.99991L6.86643 2.08036C6.49062 1.67565 6.51406 1.04292 6.91877 0.667113Z"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M0.199219 6.99991C0.199219 6.44762 0.646934 5.99991 1.19922 5.99991L12.7992 5.99991C13.3515 5.99991 13.7992 6.44762 13.7992 6.9999C13.7992 7.55219 13.3515 7.9999 12.7992 7.9999L1.19922 7.99991C0.646934 7.99991 0.199219 7.55219 0.199219 6.99991Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const WarningIcon = (props: HTMLAttributes<SVGElement>) => (
|
||||
<svg
|
||||
{...props}
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="#FFBB0C"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden
|
||||
role="img"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M9.72825 0.928245C9.58121 0.663907 9.30248 0.5 9 0.5C8.69752 0.5 8.41879 0.663907 8.27175 0.928245L0.855084 14.2616C0.711514 14.5197 0.715285 14.8345 0.864996 15.0891C1.01471 15.3437 1.28799 15.5 1.58333 15.5H16.4167C16.712 15.5 16.9853 15.3437 17.135 15.0891C17.2847 14.8345 17.2885 14.5197 17.1449 14.2616L9.72825 0.928245ZM8.99992 4.91667C8.53968 4.91667 8.16658 5.28976 8.16658 5.75V9.91667C8.16658 10.3769 8.53968 10.75 8.99992 10.75C9.46016 10.75 9.83325 10.3769 9.83325 9.91667V5.75C9.83325 5.28976 9.46016 4.91667 8.99992 4.91667ZM8.99992 13.4583C9.46016 13.4583 9.83325 13.0852 9.83325 12.625C9.83325 12.1648 9.46016 11.7917 8.99992 11.7917C8.53968 11.7917 8.16658 12.1648 8.16658 12.625C8.16658 13.0852 8.53968 13.4583 8.99992 13.4583Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
|
|
@ -9,3 +9,4 @@ export { default as Popover } from './popover/Popover'
|
|||
export { default as PopoverWithTrigger } from './popover/PopoverWithTrigger'
|
||||
export { Step, Stepper } from './stepper'
|
||||
export * as typography from './typography'
|
||||
export * from './checklist'
|
||||
|
|
|
@ -51,17 +51,22 @@ type ContainerProps = {
|
|||
children: ReactNode
|
||||
forceTheme?: ThemeType
|
||||
backgroundColor?: (theme: DefaultTheme) => string
|
||||
className?: string
|
||||
}
|
||||
|
||||
export default function Container({
|
||||
backgroundColor,
|
||||
forceTheme,
|
||||
children,
|
||||
className,
|
||||
}: ContainerProps) {
|
||||
return (
|
||||
<ForceThemeProvider forceTheme={forceTheme}>
|
||||
<OuterOuterContainer>
|
||||
<OuterContainer $backgroundColor={backgroundColor}>
|
||||
<OuterContainer
|
||||
$backgroundColor={backgroundColor}
|
||||
className={className}
|
||||
>
|
||||
<InnerContainer>{children}</InnerContainer>
|
||||
</OuterContainer>
|
||||
</OuterOuterContainer>
|
||||
|
|
|
@ -127,13 +127,9 @@ export default function Popover(
|
|||
</CloseButton>
|
||||
</CloseButtonContainer>
|
||||
)}
|
||||
{/* tabIndex -1 is for text selection in popover, see https://github.com/adobe/react-spectrum/issues/1604#issuecomment-781574668 */}
|
||||
|
||||
<PopoverContent ref={contentRef}>
|
||||
{title && (
|
||||
<H2 as="h1" {...titleProps}>
|
||||
{title}
|
||||
</H2>
|
||||
)}
|
||||
{title && <H2 {...titleProps}>{title}</H2>}
|
||||
{children}
|
||||
</PopoverContent>
|
||||
</PopoverContainer>
|
||||
|
@ -162,7 +158,7 @@ const Underlay = styled.div<UnderlayProps>`
|
|||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
overflow: auto;
|
||||
overflow: visible;
|
||||
z-index: 200; // to be in front of the menu of the Publicodes doc
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
animation: ${appear} 0.2s;
|
||||
|
|
|
@ -35,9 +35,10 @@ export default function PopoverConfirm({
|
|||
return (
|
||||
<PopoverWithTrigger trigger={trigger} small={small}>
|
||||
{(closePopover) => (
|
||||
<StyledContainer>
|
||||
<H3>{title}</H3>
|
||||
<Body>{children}</Body>
|
||||
<div>
|
||||
{title && <H3>{title}</H3>}
|
||||
|
||||
<div>{children}</div>
|
||||
|
||||
<StyledGrid container>
|
||||
<Grid item>
|
||||
|
@ -56,7 +57,7 @@ export default function PopoverConfirm({
|
|||
</Button>
|
||||
</Grid>
|
||||
</StyledGrid>
|
||||
</StyledContainer>
|
||||
</div>
|
||||
)}
|
||||
</PopoverWithTrigger>
|
||||
)
|
||||
|
@ -68,7 +69,3 @@ const StyledGrid = styled(Grid)`
|
|||
gap: ${({ theme }) => theme.spacings.md};
|
||||
margin-top: ${({ theme }) => theme.spacings.xl};
|
||||
`
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
padding: ${({ theme }) => theme.spacings.xxl};
|
||||
`
|
||||
|
|
|
@ -1,12 +1,47 @@
|
|||
import styled from 'styled-components'
|
||||
|
||||
export const Tag = styled.div`
|
||||
import { baseTheme, getColorGroup } from '../theme'
|
||||
|
||||
export type TagType = keyof typeof baseTheme.colors.bases &
|
||||
keyof typeof baseTheme.colors.extended &
|
||||
keyof typeof baseTheme.colors.publics
|
||||
|
||||
type SizeType = 'sm' | 'md' | 'lg'
|
||||
|
||||
const lightColors = ['grey']
|
||||
|
||||
export const Tag = styled.div<{ $color?: TagType; $size?: SizeType }>`
|
||||
font-family: ${({ theme }) => theme.fonts.main};
|
||||
background-color: ${({ theme }) => theme.colors.bases.primary[100]};
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: fit-content;
|
||||
padding: 0.25rem 1rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
color: ${({ theme }) => theme.colors.extended.grey[800]};
|
||||
font-weight: 500;
|
||||
background-color: ${({ theme, $color }) =>
|
||||
$color
|
||||
? theme.colors[getColorGroup($color)][$color][
|
||||
lightColors.includes($color) ? 300 : 100
|
||||
]
|
||||
: theme.colors.bases.primary[100]};
|
||||
color: ${({ theme, $color }) =>
|
||||
$color
|
||||
? theme.colors[getColorGroup($color)][$color][600]
|
||||
: theme.colors.extended.grey[800]};
|
||||
font-size: ${({ $size }) => {
|
||||
switch ($size) {
|
||||
case 'sm':
|
||||
return '0.75rem'
|
||||
case 'md':
|
||||
default:
|
||||
return '1rem'
|
||||
}
|
||||
}};
|
||||
svg {
|
||||
fill: ${({ theme, $color }) =>
|
||||
$color
|
||||
? theme.colors[getColorGroup($color)][$color][600]
|
||||
: theme.colors.extended.grey[800]};
|
||||
}
|
||||
`
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { Theme } from '@/types/styled'
|
||||
|
||||
const baseTheme = {
|
||||
import { TagType } from './tag'
|
||||
|
||||
export const baseTheme = {
|
||||
colors: {
|
||||
bases: {
|
||||
primary: {
|
||||
|
@ -183,6 +185,19 @@ const baseTheme = {
|
|||
},
|
||||
}
|
||||
|
||||
export type ColorGroups = Array<keyof typeof baseTheme.colors>
|
||||
|
||||
export const getColorGroup = (color: TagType) => {
|
||||
const colorGroups: ColorGroups = Object.keys(baseTheme.colors).map(
|
||||
(colorGroup) => colorGroup as keyof typeof baseTheme.colors
|
||||
)
|
||||
|
||||
return colorGroups.find(
|
||||
(colorGroup: keyof typeof baseTheme.colors) =>
|
||||
!!baseTheme.colors[colorGroup]?.[color]
|
||||
) as keyof typeof baseTheme.colors
|
||||
}
|
||||
|
||||
// We use the Grid from material-ui, we need to uniformise
|
||||
// breakpoints and spacing with the Urssaf design system
|
||||
export type SpacingKey = keyof typeof baseTheme.breakpointsWidth
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
import React, { ReactNode } from 'react'
|
||||
import { Tooltip as RTooltip } from 'react-tooltip'
|
||||
|
||||
import 'react-tooltip/dist/react-tooltip.css'
|
||||
|
||||
import styled from 'styled-components'
|
||||
|
||||
export const Tooltip = ({
|
||||
children,
|
||||
tooltip,
|
||||
className,
|
||||
id,
|
||||
}: {
|
||||
children: ReactNode
|
||||
tooltip: ReactNode
|
||||
className?: string
|
||||
// A11y : préciser un aria-describedby sur l'élément visé par le tooltip
|
||||
id: string
|
||||
}) => {
|
||||
return (
|
||||
<StyledSpan>
|
||||
{React.Children.map(children, (child) => {
|
||||
if (React.isValidElement(child)) {
|
||||
return React.cloneElement(child, { id } as {
|
||||
id: string
|
||||
})
|
||||
}
|
||||
})}
|
||||
<StyledRTooltip
|
||||
anchorId={id}
|
||||
className={className}
|
||||
id={`${id}-description`}
|
||||
>
|
||||
{tooltip}
|
||||
</StyledRTooltip>
|
||||
</StyledSpan>
|
||||
)
|
||||
}
|
||||
|
||||
const StyledRTooltip = styled(RTooltip)`
|
||||
max-width: 20rem;
|
||||
font-size: 0.75rem;
|
||||
`
|
||||
const StyledSpan = styled.span`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.react-tooltip {
|
||||
opacity: 1 !important;
|
||||
background: ${({ theme }) => theme.colors.extended.grey[800]};
|
||||
color: ${({ theme }) => theme.colors.extended.grey[100]};
|
||||
z-index: 100;
|
||||
}
|
||||
`
|
|
@ -0,0 +1,31 @@
|
|||
import { ComponentMeta, ComponentStory } from '@storybook/react'
|
||||
|
||||
import { Tooltip } from '@/design-system/tooltip'
|
||||
|
||||
// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
|
||||
export default {
|
||||
component: Tooltip,
|
||||
// More on argTypes: https://storybook.js.org/docs/react/api/argtypes
|
||||
argTypes: {
|
||||
children: {
|
||||
type: 'string',
|
||||
},
|
||||
tooltip: {
|
||||
type: 'string',
|
||||
},
|
||||
id: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof Tooltip>
|
||||
|
||||
// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
|
||||
const Template: ComponentStory<typeof Tooltip> = (args) => <Tooltip {...args} />
|
||||
|
||||
export const Basic = Template.bind({})
|
||||
// More on args: https://storybook.js.org/docs/react/writing-stories/args
|
||||
Basic.args = {
|
||||
children: 'Passez la souris sur moi',
|
||||
tooltip: 'Coucou !',
|
||||
id: 'test-tooltip',
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export { Tooltip } from './Tooltip'
|
|
@ -4,6 +4,7 @@ import { useLocation, useNavigationType } from 'react-router-dom'
|
|||
import { debounce, getSessionStorage } from '@/utils'
|
||||
|
||||
const POP_ACTION_LABEL = 'POP'
|
||||
const REPLACE_ACTION_LABEL = 'REPLACE'
|
||||
const sessionStorage = getSessionStorage()
|
||||
|
||||
export const useSaveAndRestoreScrollPosition = () => {
|
||||
|
@ -13,7 +14,11 @@ export const useSaveAndRestoreScrollPosition = () => {
|
|||
useEffect(() => {
|
||||
const scrollPosition = sessionStorage?.getItem(location.pathname)
|
||||
|
||||
if (scrollPosition && navigationType === POP_ACTION_LABEL) {
|
||||
if (
|
||||
scrollPosition &&
|
||||
(navigationType === POP_ACTION_LABEL ||
|
||||
navigationType === REPLACE_ACTION_LABEL)
|
||||
) {
|
||||
window.scrollTo(0, parseInt(scrollPosition))
|
||||
}
|
||||
}, [location, navigationType])
|
||||
|
|
|
@ -5202,48 +5202,6 @@ protection sociale:
|
|||
formation professionnelle et le transport.
|
||||
titre.en: social welfare
|
||||
titre.fr: protection sociale
|
||||
protection sociale . accidents du travail et maladies professionnelles:
|
||||
description.en: >
|
||||
[automatic] Occupational injury and disease insurance is the
|
||||
oldest branch of Social Security: it is based on principles dating back to
|
||||
1898 and included in the law of December 31, 1946.
|
||||
|
||||
|
||||
[🎞️ See the video](https://www.youtube.com/watch?v=NaGI_deZJD8 )
|
||||
|
||||
|
||||
The AT/MP contribution covers the risks of accidents at work, commuting accidents and occupational diseases for employees under the general scheme.
|
||||
|
||||
|
||||
To find out about occupational risks and set up prevention actions, the [AT/MP account](https://www.ameli.fr/entreprise/votre-entreprise/compte-atmp/ouvrir-compte-atmp) is a service open to all companies under the general Social Security scheme.
|
||||
|
||||
|
||||
In the event of an occupational injury, medical and surgical care is fully reimbursed within the limits of the Social Security rates.
|
||||
description.fr: >
|
||||
L’assurance AT/MP (accident du travail et maladie
|
||||
professionnelle) est la plus ancienne branche de la Sécurité sociale : elle
|
||||
relève de principes qui remontent à l’année 1898 et qui ont été repris dans
|
||||
la loi du 31 décembre 1946.
|
||||
|
||||
|
||||
[🎞️ Voir la vidéo](https://www.youtube.com/watch?v=NaGI_deZJD8 )
|
||||
|
||||
|
||||
La cotisation AT/MP couvre les risques accidents du travail, accidents de trajet et maladies professionnelles pour les salariés relevant du régime général.
|
||||
|
||||
|
||||
Pour connaître les risques professionnels et mettre en place des actions de prévention, le [compte AT/MP](https://www.ameli.fr/entreprise/votre-entreprise/compte-atmp/ouvrir-compte-atmp) est un service ouvert à toutes les entreprises du régime général de la Sécurité sociale.
|
||||
|
||||
|
||||
En cas d’AT/MP, les soins médicaux et chirurgicaux sont remboursés intégralement dans la limite des tarifs de la Sécurité sociale.
|
||||
note.en: |
|
||||
[automatic] The rate is 80% from the 29th day of the stoppage.
|
||||
note.fr: |
|
||||
Le taux est de 80% à partir du 29e jour d'arrêt.
|
||||
résumé.en: Provides comprehensive coverage for occupational diseases or accidents.
|
||||
résumé.fr: Offre une couverture complète des maladies ou accidents du travail.
|
||||
titre.en: Work accidents / occupational diseases
|
||||
titre.fr: accidents du travail et maladies professionnelles
|
||||
protection sociale . assurance chômage:
|
||||
description.en: >
|
||||
Since 1958, the Unemployment Insurance has been protecting all
|
||||
|
@ -5408,9 +5366,33 @@ protection sociale . maladie:
|
|||
des maladies graves comme les séjours à l'hôpital.
|
||||
titre.en: '[automatic] health insurance'
|
||||
titre.fr: assurance maladie
|
||||
protection sociale . maladie . ATMP:
|
||||
titre.en: '[automatic] Workplace injury and occupational disease'
|
||||
titre.fr: Accident du travail et maladie professionnelle
|
||||
protection sociale . maladie . accidents du travail et maladies professionnelles:
|
||||
description.en: >
|
||||
[automatic] Have you suffered an accident at work or an
|
||||
occupational disease?
|
||||
|
||||
Your medical expenses are covered at 100%.
|
||||
|
||||
|
||||
To compensate for your loss of salary, you can receive a daily allowance.
|
||||
|
||||
If you are declared unfit as a result of your accident/illness, you may receive a temporary incapacity benefit.
|
||||
description.fr: >
|
||||
Vous avez subi un accident du travail ou êtes atteint d’une
|
||||
maladie professionnelle ?
|
||||
|
||||
Vos frais médicaux sont pris en charge à 100 %.
|
||||
|
||||
|
||||
Pour compenser votre perte de salaire, vous pouvez percevoir des indemnités journalières.
|
||||
|
||||
Si vous êtes déclaré inapte suite à votre accident / maladie, vous pouvez recevoir une indemnité temporaire d'inaptitude.
|
||||
résumé.en:
|
||||
'[automatic] Provides comprehensive coverage for work-related illness
|
||||
or injury.'
|
||||
résumé.fr: Offre une couverture complète des maladies ou accidents du travail.
|
||||
titre.en: '[automatic] work accidents and occupational diseases'
|
||||
titre.fr: accidents du travail et maladies professionnelles
|
||||
protection sociale . maladie . arrêt maladie:
|
||||
description.en: >-
|
||||
[automatic] If you are off work due to illness, you are entitled
|
||||
|
@ -5432,6 +5414,9 @@ protection sociale . maladie . arrêt maladie . indépendant:
|
|||
protection sociale . maladie . arrêt maladie . salarié:
|
||||
titre.en: '[automatic] employee'
|
||||
titre.fr: salarié
|
||||
protection sociale . maladie . maternité paternité adoption:
|
||||
titre.en: '[automatic] maternity and paternity leave benefits'
|
||||
titre.fr: indemnités congé maternité paternité adoption
|
||||
protection sociale . retraite:
|
||||
description.en: >
|
||||
[automatic] ### A mandatory system ...
|
||||
|
@ -5544,9 +5529,9 @@ protection sociale . retraite . base:
|
|||
|
||||
This estimate of your retirement pension is calculated based on the following principles:
|
||||
|
||||
- Your earnings are calculated on the basis of your best 25 years
|
||||
- The earnings calculated in the simulator correspond to your best 25 years
|
||||
|
||||
- You have contributed enough quarters and you leave at the age required to benefit from the full rate
|
||||
- You have contributed enough quarters and you are leaving at the age required to benefit from the full rate
|
||||
description.fr: >
|
||||
Le montant de votre pension pour la retraite de base est calculé
|
||||
à partir la moyenne de vos revenus des 25 meilleures années.
|
||||
|
@ -5554,7 +5539,7 @@ protection sociale . retraite . base:
|
|||
|
||||
Cet estimation de votre pension de retraite est calculée en se basant sur les principes suivants :
|
||||
|
||||
- La rémunération calculée correspond à celle de vos 25 meilleures années
|
||||
- La rémunération calculée dans le simulateur correspond à celle de vos 25 meilleures années
|
||||
|
||||
- Vous avez cotisé suffisement de trimestres et vous partez à l'âge requis pour bénéficier du taux plein
|
||||
résumé.en: '[automatic] Full basic retirement pension assuming your earnings
|
||||
|
|
|
@ -191,6 +191,8 @@ Mois non concerné: Month not concerned
|
|||
Mon entreprise: My company
|
||||
Mon revenu: My income
|
||||
Montant: Amount
|
||||
Montant annuel: Yearly amount
|
||||
Montant mensuel: Monthly amount
|
||||
Montant de l'impôt sur les sociétés: Amount of corporate income tax
|
||||
Montant de l’exonération sociale liée à la crise sanitaire pour les cotisations de l’année 2021: Amount of the health crisis exemption for contributions in 2021
|
||||
Montant de l’exonération sociale liée à la crise sanitaire sur l’année 2021: Amount of the social exemption related to the health crisis in 2021
|
||||
|
|
|
@ -137,6 +137,8 @@ Modifier l'entreprise: Modifier l'entreprise
|
|||
Modifier mes réponses: Modifier mes réponses
|
||||
Mois non concerné: Mois non concerné
|
||||
Mon entreprise: Mon entreprise
|
||||
Montant annuel: Montant annuel
|
||||
Montant mensuel: Montant mensuel
|
||||
Montant de l'impôt sur les sociétés: Montant de l'impôt sur les sociétés
|
||||
Montant de l’exonération sociale liée à la crise sanitaire pour les cotisations de l’année 2021:
|
||||
Montant de l’exonération sociale liée à la crise sanitaire pour les
|
||||
|
|
|
@ -86,7 +86,7 @@ export default function Landing() {
|
|||
>
|
||||
<SimulateurCard {...simulators.salarié} />
|
||||
<SimulateurCard {...simulators['auto-entrepreneur']} />
|
||||
<SimulateurCard {...simulators['profession-libérale']} />
|
||||
<SimulateurCard {...simulators['comparaison-statuts']} />
|
||||
|
||||
<Grid
|
||||
item
|
||||
|
|
|
@ -0,0 +1,582 @@
|
|||
import Engine, { PublicodesExpression } from 'publicodes'
|
||||
import { useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { DottedName } from '@/../../modele-social'
|
||||
import { answerQuestion } from '@/actions/actions'
|
||||
import Value from '@/components/EngineValue'
|
||||
import { SwitchInput } from '@/components/conversation/ChoicesInput'
|
||||
import { ExplicableRule } from '@/components/conversation/Explicable'
|
||||
import RuleInput from '@/components/conversation/RuleInput'
|
||||
import { useEngine } from '@/components/utils/EngineContext'
|
||||
import { Message } from '@/design-system'
|
||||
import { Button } from '@/design-system/buttons'
|
||||
import { Drawer } from '@/design-system/drawer'
|
||||
import { ArrowRightIcon, InfoIcon } from '@/design-system/icons'
|
||||
import { Grid, Spacing } from '@/design-system/layout'
|
||||
import { Tag, TagType } from '@/design-system/tag'
|
||||
import { Tooltip } from '@/design-system/tooltip'
|
||||
import { Strong } from '@/design-system/typography'
|
||||
import { H1, H4, H5 } from '@/design-system/typography/heading'
|
||||
import { Link, StyledLink } from '@/design-system/typography/link'
|
||||
import { Body } from '@/design-system/typography/paragraphs'
|
||||
|
||||
import { useCasParticuliers } from '../contexts/CasParticuliers'
|
||||
import { StatusTagIcon } from './StatusCard'
|
||||
|
||||
const DOTTEDNAME_SOCIETE_IMPOT = 'entreprise . imposition'
|
||||
const DOTTEDNAME_SOCIETE_VERSEMENT_LIBERATOIRE =
|
||||
'dirigeant . auto-entrepreneur . impôt . versement libératoire'
|
||||
const DOTTEDNAME_ACRE = 'dirigeant . exonérations . ACRE'
|
||||
|
||||
const AllerPlusLoinRevenus = ({
|
||||
engines: [assimiléEngine, autoEntrepreneurEngine, indépendantEngine],
|
||||
}: {
|
||||
engines: [Engine<DottedName>, Engine<DottedName>, Engine<DottedName>]
|
||||
}) => {
|
||||
const defaultValueImpot = useEngine().evaluate(
|
||||
DOTTEDNAME_SOCIETE_IMPOT
|
||||
).nodeValue
|
||||
const defaultValueVersementLiberatoire = autoEntrepreneurEngine.evaluate(
|
||||
DOTTEDNAME_SOCIETE_VERSEMENT_LIBERATOIRE
|
||||
).nodeValue
|
||||
const defaultValueACRE = assimiléEngine.evaluate(DOTTEDNAME_ACRE).nodeValue
|
||||
|
||||
const [impotValue, setImpotValue] = useState(
|
||||
`'${String(defaultValueImpot)}'` || "'IS'"
|
||||
)
|
||||
const [versementLiberatoireValue, setVersementLiberatoireValue] = useState(
|
||||
defaultValueVersementLiberatoire
|
||||
)
|
||||
const [acreValue, setAcreValue] = useState(defaultValueACRE)
|
||||
const { isAutoEntrepreneurACREEnabled, setIsAutoEntrepreneurACREEnabled } =
|
||||
useCasParticuliers()
|
||||
|
||||
const [AEAcreValue, setAEAcreValue] = useState<boolean | null>(null)
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
const dispatch = useDispatch()
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
trigger={(buttonProps: { onClick: () => void }) => (
|
||||
<Button color="secondary" light size="XS" {...buttonProps}>
|
||||
<Trans>Aller plus loin</Trans> <StyledArrowRightIcon />
|
||||
</Button>
|
||||
)}
|
||||
confirmLabel="Enregistrer les options"
|
||||
onConfirm={() => {
|
||||
dispatch(
|
||||
answerQuestion(
|
||||
DOTTEDNAME_SOCIETE_IMPOT,
|
||||
impotValue as PublicodesExpression
|
||||
)
|
||||
)
|
||||
|
||||
const versementLibératoireValuePassed =
|
||||
versementLiberatoireValue === null
|
||||
? defaultValueVersementLiberatoire
|
||||
: versementLiberatoireValue
|
||||
dispatch(
|
||||
answerQuestion(
|
||||
DOTTEDNAME_SOCIETE_VERSEMENT_LIBERATOIRE,
|
||||
versementLibératoireValuePassed ? 'oui' : 'non'
|
||||
)
|
||||
)
|
||||
|
||||
const acreValuePassed =
|
||||
acreValue === null ? defaultValueACRE : acreValue
|
||||
dispatch(
|
||||
answerQuestion(DOTTEDNAME_ACRE, acreValuePassed ? 'oui' : 'non')
|
||||
)
|
||||
|
||||
if (AEAcreValue !== null) {
|
||||
setIsAutoEntrepreneurACREEnabled(AEAcreValue)
|
||||
}
|
||||
}}
|
||||
onCancel={() => {
|
||||
setAcreValue(null)
|
||||
setVersementLiberatoireValue(null)
|
||||
}}
|
||||
>
|
||||
<>
|
||||
<H1>
|
||||
<Trans>Aller plus loin sur les revenus</Trans>
|
||||
</H1>
|
||||
<H4
|
||||
as="h2"
|
||||
css={`
|
||||
margin-bottom: 1rem;
|
||||
`}
|
||||
>
|
||||
<Trans>Calculer vos revenus</Trans>
|
||||
</H4>
|
||||
<StyledTable>
|
||||
<caption className="sr-only">
|
||||
{t(
|
||||
'comparateur.allerPlusLoin.tableCaption',
|
||||
"Tableau affichant le détail du calcul du revenu net pour la SASU, l'entreprise individuelle (EI) et l'auto-entreprise (AE)."
|
||||
)}
|
||||
</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="sr-only">Type de structure</th>
|
||||
<th scope="col">
|
||||
<span className="table-title-sasu">
|
||||
<StatusTagIcon status="sasu" /> SASU
|
||||
</span>
|
||||
</th>
|
||||
|
||||
<th scope="col">
|
||||
<Tooltip
|
||||
tooltip="Entreprise individuelle"
|
||||
id="tooltip-ei-table"
|
||||
>
|
||||
<span className="table-title-ei">
|
||||
<StatusTagIcon status="ei" /> EI
|
||||
</span>
|
||||
</Tooltip>
|
||||
</th>
|
||||
|
||||
<th scope="col">
|
||||
<Tooltip tooltip="Auto-entreprise" id="tooltip-ae-table">
|
||||
<span className="table-title-ae">
|
||||
<StatusTagIcon status="ae" /> AE
|
||||
</span>
|
||||
</Tooltip>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<Minus
|
||||
css={`
|
||||
opacity: 0;
|
||||
`}
|
||||
aria-hidden
|
||||
>
|
||||
-
|
||||
</Minus>{' '}
|
||||
<Trans>Chiffre d'affaires</Trans>
|
||||
</th>
|
||||
<td colSpan={3}>
|
||||
<StyledTag $color={'grey' as TagType}>
|
||||
<Value
|
||||
expression="entreprise . chiffre d'affaires"
|
||||
displayedUnit="€"
|
||||
linkToRule={false}
|
||||
/>
|
||||
</StyledTag>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<Minus aria-label={t('moins')}>-</Minus> <Trans>Charges</Trans>
|
||||
</th>
|
||||
<td colSpan={3}>
|
||||
<StyledTag $color={'grey' as TagType}>
|
||||
<Value
|
||||
expression="entreprise . charges"
|
||||
unit="€/an"
|
||||
displayedUnit="€"
|
||||
linkToRule={false}
|
||||
/>
|
||||
</StyledTag>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<Minus aria-label={t('moins')}>-</Minus>{' '}
|
||||
<Trans>Cotisations</Trans>
|
||||
</th>
|
||||
<td>
|
||||
<StyledTag $color={'secondary' as TagType}>
|
||||
<Value
|
||||
expression="dirigeant . rémunération . cotisations"
|
||||
engine={assimiléEngine}
|
||||
unit="€/an"
|
||||
displayedUnit="€"
|
||||
linkToRule={false}
|
||||
/>
|
||||
</StyledTag>
|
||||
</td>
|
||||
<td>
|
||||
<StyledTag $color={'independant' as TagType}>
|
||||
<Value
|
||||
expression="dirigeant . rémunération . cotisations"
|
||||
engine={indépendantEngine}
|
||||
unit="€/an"
|
||||
displayedUnit="€"
|
||||
linkToRule={false}
|
||||
/>
|
||||
</StyledTag>
|
||||
</td>
|
||||
<td>
|
||||
<StyledTag $color={'tertiary' as TagType}>
|
||||
<Value
|
||||
expression="dirigeant . rémunération . cotisations"
|
||||
engine={autoEntrepreneurEngine}
|
||||
unit="€/an"
|
||||
displayedUnit="€"
|
||||
linkToRule={false}
|
||||
/>
|
||||
</StyledTag>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<Minus aria-label={t('moins')}>-</Minus> <Trans>Impôts</Trans>
|
||||
</th>
|
||||
<td>
|
||||
<StyledTag $color={'secondary' as TagType}>
|
||||
<Value
|
||||
expression="dirigeant . rémunération . cotisations"
|
||||
engine={assimiléEngine}
|
||||
unit="€/an"
|
||||
displayedUnit="€"
|
||||
linkToRule={false}
|
||||
/>
|
||||
</StyledTag>
|
||||
</td>
|
||||
<td>
|
||||
<StyledTag $color={'independant' as TagType}>
|
||||
<Value
|
||||
expression="dirigeant . rémunération . cotisations"
|
||||
engine={indépendantEngine}
|
||||
unit="€/an"
|
||||
displayedUnit="€"
|
||||
linkToRule={false}
|
||||
/>
|
||||
</StyledTag>
|
||||
</td>
|
||||
<td>
|
||||
<StyledTag $color={'tertiary' as TagType}>
|
||||
<Value
|
||||
expression="dirigeant . rémunération . cotisations"
|
||||
engine={autoEntrepreneurEngine}
|
||||
unit="€/an"
|
||||
displayedUnit="€"
|
||||
linkToRule={false}
|
||||
/>
|
||||
</StyledTag>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<Minus
|
||||
css={`
|
||||
opacity: 0;
|
||||
`}
|
||||
aria-hidden
|
||||
>
|
||||
-
|
||||
</Minus>{' '}
|
||||
<StyledStrong>
|
||||
<Trans>Revenu net</Trans>
|
||||
</StyledStrong>
|
||||
</th>
|
||||
<td>
|
||||
<StyledTag $color={'secondary' as TagType}>
|
||||
<Strong>
|
||||
<Value
|
||||
expression="dirigeant . rémunération . net . après impôt"
|
||||
engine={assimiléEngine}
|
||||
unit="€/an"
|
||||
displayedUnit="€"
|
||||
linkToRule={false}
|
||||
/>
|
||||
</Strong>
|
||||
</StyledTag>
|
||||
</td>
|
||||
<td>
|
||||
<StyledTag $color={'independant' as TagType}>
|
||||
<Strong>
|
||||
<Value
|
||||
expression="dirigeant . rémunération . net . après impôt"
|
||||
engine={indépendantEngine}
|
||||
unit="€/an"
|
||||
displayedUnit="€"
|
||||
linkToRule={false}
|
||||
/>
|
||||
</Strong>
|
||||
</StyledTag>
|
||||
</td>
|
||||
<td>
|
||||
<StyledTag $color={'tertiary' as TagType}>
|
||||
<Strong>
|
||||
<Value
|
||||
expression="dirigeant . rémunération . net . après impôt"
|
||||
engine={autoEntrepreneurEngine}
|
||||
unit="€/an"
|
||||
displayedUnit="€"
|
||||
linkToRule={false}
|
||||
/>
|
||||
</Strong>
|
||||
</StyledTag>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</StyledTable>
|
||||
<Spacing md />
|
||||
<Flex>
|
||||
<H4 as="h2">Bénéficier de l'ACRE</H4>
|
||||
<ExplicableRule
|
||||
dottedName="dirigeant . exonérations . ACRE"
|
||||
title="Bénéficier de l'ACRE"
|
||||
/>
|
||||
</Flex>
|
||||
|
||||
<Body>
|
||||
L'aide à la création ou à la reprise d'une entreprise (Acre) consiste
|
||||
en une <Strong>exonération partielle de charges sociales</Strong>,
|
||||
dite exonération de début d'activité <Strong>pendant 12 mois</Strong>.
|
||||
</Body>
|
||||
{
|
||||
// TODO : décommenter une fois le simulateur créé
|
||||
<Button
|
||||
href="https://entreprendre.service-public.fr/vosdroits/F23282"
|
||||
aria-label={t('En savoir plus, nouvelle fenêtre')}
|
||||
color="secondary"
|
||||
light
|
||||
size="XS"
|
||||
>
|
||||
<Trans>En savoir plus</Trans>
|
||||
</Button>
|
||||
}
|
||||
<H5 as="h3">Choisir mon option de simulation</H5>
|
||||
<div aria-live="polite">
|
||||
<FlexCentered>
|
||||
<SwitchInput
|
||||
id="activation-acre"
|
||||
onChange={(value: boolean) => setAcreValue(value)}
|
||||
defaultSelected={defaultValueACRE as boolean}
|
||||
/>
|
||||
<Label htmlFor="activation-acre">
|
||||
Activer l'ACRE dans la simulation
|
||||
</Label>
|
||||
</FlexCentered>
|
||||
|
||||
{(acreValue || defaultValueACRE) && (
|
||||
<>
|
||||
<Body>
|
||||
Les{' '}
|
||||
<StyledLink href="https://www.urssaf.fr/portail/home/independant/je-beneficie-dexonerations/accre/qui-peut-en-beneficier.html">
|
||||
conditions d'accès
|
||||
</StyledLink>{' '}
|
||||
à l'ACRE sont plus restrictives pour les auto-entrepreneurs.
|
||||
</Body>
|
||||
<FlexCentered>
|
||||
<SwitchInput
|
||||
id="activation-acre-ae"
|
||||
onChange={(value: boolean) => setAEAcreValue(value)}
|
||||
defaultSelected={isAutoEntrepreneurACREEnabled}
|
||||
/>
|
||||
<Label htmlFor="activation-acre-ae">
|
||||
Je suis éligible à l'ACRE pour mon auto-entreprise
|
||||
</Label>
|
||||
</FlexCentered>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Spacing md />
|
||||
<H4 as="h2">
|
||||
Impôt sur le revenu, impôt sur les sociétés : que choisir ?
|
||||
</H4>
|
||||
<Body>
|
||||
L’EI et la SASU permettent de{' '}
|
||||
<Strong>
|
||||
choisir entre l’imposition sur les sociétés et sur le revenu
|
||||
</Strong>{' '}
|
||||
durant les 5 premières années. En auto-entreprise (AE), c’est l’
|
||||
<Strong>impôt sur le revenu</Strong> qui est appliqué automatiquement
|
||||
; dans certaines situations, vous pouvez aussi opter pour le{' '}
|
||||
<Strong>
|
||||
<Link href="https://www.impots.gouv.fr/professionnel/le-versement-liberatoire">
|
||||
versement libératoire
|
||||
</Link>
|
||||
</Strong>
|
||||
.
|
||||
</Body>
|
||||
<H5 as="h3">Choisir mon option de simulation (pour EI)</H5>
|
||||
<Message type="secondary">
|
||||
<Grid
|
||||
container
|
||||
css={`
|
||||
flex-wrap: nowrap;
|
||||
align-items: baseline;
|
||||
`}
|
||||
spacing={3}
|
||||
>
|
||||
<Grid item>
|
||||
<InfoIcon
|
||||
css={`
|
||||
padding-top: 0.15rem;
|
||||
display: inline-block;
|
||||
`}
|
||||
aria-label={t('Message à caractère informatif')}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Body
|
||||
css={`
|
||||
font-size: 0.875rem;
|
||||
`}
|
||||
>
|
||||
<Trans>
|
||||
À ce jour, ce comparateur ne prend pas en compte le calcul de
|
||||
l'impôt sur le revenu pour les SASU. La modification du
|
||||
paramètre ci-dessous influera donc uniquement les calculs liés
|
||||
au statut d'entreprise individuelle (EI).
|
||||
</Trans>
|
||||
</Body>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Message>
|
||||
<RuleInput
|
||||
dottedName={DOTTEDNAME_SOCIETE_IMPOT}
|
||||
onChange={(value: PublicodesExpression | undefined) => {
|
||||
setImpotValue(String(value))
|
||||
}}
|
||||
key="imposition"
|
||||
aria-labelledby="questionHeader"
|
||||
engine={indépendantEngine}
|
||||
/>
|
||||
|
||||
<H5 as="h3">
|
||||
Choisir mon option de versement libératoire (pour AE){' '}
|
||||
<ExplicableRule
|
||||
dottedName={DOTTEDNAME_SOCIETE_VERSEMENT_LIBERATOIRE}
|
||||
/>
|
||||
</H5>
|
||||
<FlexCentered>
|
||||
<SwitchInput
|
||||
id="versement-liberatoire"
|
||||
onChange={setVersementLiberatoireValue}
|
||||
defaultSelected={defaultValueVersementLiberatoire as boolean}
|
||||
/>
|
||||
<Label htmlFor="versement-liberatoire">
|
||||
Activer le versement libératoire dans la simulation.
|
||||
</Label>
|
||||
</FlexCentered>
|
||||
</>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
|
||||
const StyledTag = styled(Tag)<{ $color: TagType }>`
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
font-size: 0.75rem;
|
||||
`
|
||||
|
||||
const Minus = styled.span`
|
||||
color: ${({ theme }) => theme.colors.bases.secondary[500]};
|
||||
margin-right: ${({ theme }) => theme.spacings.sm};
|
||||
`
|
||||
|
||||
const StyledStrong = styled(Strong)`
|
||||
font-family: ${({ theme }) => theme.fonts.main};
|
||||
`
|
||||
|
||||
const Flex = styled.div`
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
`
|
||||
|
||||
const FlexCentered = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const Label = styled.label`
|
||||
margin-left: ${({ theme }) => theme.spacings.md};
|
||||
font-family: ${({ theme }) => theme.fonts.main};
|
||||
font-size: 1rem;
|
||||
`
|
||||
|
||||
const StyledArrowRightIcon = styled(ArrowRightIcon)`
|
||||
margin-left: ${({ theme }) => theme.spacings.sm};
|
||||
`
|
||||
|
||||
const StyledTable = styled.table`
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
font-family: ${({ theme }) => theme.fonts.main};
|
||||
border-collapse: separate;
|
||||
border-spacing: 0.5rem;
|
||||
border: transparent;
|
||||
|
||||
tr {
|
||||
border-spacing: ${({ theme }) => theme.spacings.md}!important;
|
||||
}
|
||||
|
||||
thead th {
|
||||
text-align: center;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.table-title-sasu {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: ${({ theme }) => theme.colors.bases.secondary[600]};
|
||||
svg {
|
||||
fill: ${({ theme }) => theme.colors.bases.secondary[600]};
|
||||
margin-right: ${({ theme }) => theme.spacings.xxs};
|
||||
}
|
||||
}
|
||||
.table-title-ei {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: ${({ theme }) => theme.colors.publics.independant[600]};
|
||||
svg {
|
||||
fill: ${({ theme }) => theme.colors.publics.independant[600]};
|
||||
margin-right: ${({ theme }) => theme.spacings.xxs};
|
||||
}
|
||||
}
|
||||
.table-title-ae {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: ${({ theme }) => theme.colors.bases.tertiary[500]};
|
||||
svg {
|
||||
fill: ${({ theme }) => theme.colors.bases.tertiary[500]};
|
||||
margin-right: ${({ theme }) => theme.spacings.xxs};
|
||||
}
|
||||
}
|
||||
|
||||
tbody th {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
tbody tr:last-of-type td {
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
tfoot {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
tfoot:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background-color: ${({ theme }) => theme.colors.extended.grey[500]};
|
||||
}
|
||||
tfoot td,
|
||||
tfoot th {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
`
|
||||
|
||||
export default AllerPlusLoinRevenus
|
|
@ -0,0 +1,123 @@
|
|||
import Engine from 'publicodes'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Route, Routes, redirect, useNavigate } from 'react-router-dom'
|
||||
|
||||
import { DottedName } from '@/../../modele-social'
|
||||
import PeriodSwitch from '@/components/PeriodSwitch'
|
||||
import Simulation, {
|
||||
SimulationGoal,
|
||||
SimulationGoals,
|
||||
} from '@/components/Simulation'
|
||||
import { Spacing } from '@/design-system/layout'
|
||||
import Popover from '@/design-system/popover/Popover'
|
||||
import Documentation from '@/pages/Documentation'
|
||||
import { useSitePaths } from '@/sitePaths'
|
||||
|
||||
import Détails from './Détails'
|
||||
import Résultats from './Résultats'
|
||||
|
||||
type ComparateurProps = {
|
||||
engines: [Engine<DottedName>, Engine<DottedName>, Engine<DottedName>]
|
||||
}
|
||||
|
||||
function Comparateur({ engines }: ComparateurProps) {
|
||||
const { t } = useTranslation()
|
||||
const navigate = useNavigate()
|
||||
const [assimiléEngine, autoEntrepreneurEngine, indépendantEngine] = engines
|
||||
|
||||
const { absoluteSitePaths } = useSitePaths()
|
||||
|
||||
return (
|
||||
<>
|
||||
<Simulation
|
||||
engines={engines}
|
||||
hideDetails
|
||||
showQuestionsFromBeginning
|
||||
fullWidth
|
||||
id="simulation-comparateur"
|
||||
>
|
||||
<SimulationGoals
|
||||
toggles={<PeriodSwitch />}
|
||||
legend={'Estimations sur votre rémunération brute et vos charges'}
|
||||
>
|
||||
<SimulationGoal
|
||||
dottedName="entreprise . chiffre d'affaires"
|
||||
isInfoMode
|
||||
label={t("Chiffre d'affaires estimé")}
|
||||
/>
|
||||
<SimulationGoal dottedName="entreprise . charges" isInfoMode />
|
||||
</SimulationGoals>
|
||||
</Simulation>
|
||||
<Spacing md />
|
||||
<Résultats engines={engines} />
|
||||
<Détails engines={engines} />
|
||||
<Routes>
|
||||
<Route
|
||||
path="SASU/*"
|
||||
element={
|
||||
<div>
|
||||
<Popover
|
||||
isOpen
|
||||
isDismissable
|
||||
onClose={() => {
|
||||
navigate(absoluteSitePaths.simulateurs.comparaison, {
|
||||
replace: true,
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Documentation
|
||||
engine={assimiléEngine}
|
||||
documentationPath="/simulateurs/comparaison-régimes-sociaux/SASU"
|
||||
/>
|
||||
</Popover>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="EI/*"
|
||||
element={
|
||||
<div>
|
||||
<Popover
|
||||
isOpen
|
||||
isDismissable
|
||||
onClose={() => {
|
||||
navigate(absoluteSitePaths.simulateurs.comparaison, {
|
||||
replace: true,
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Documentation
|
||||
engine={indépendantEngine}
|
||||
documentationPath="/simulateurs/comparaison-régimes-sociaux/EI"
|
||||
/>
|
||||
</Popover>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="auto-entrepreneur/*"
|
||||
element={
|
||||
<div>
|
||||
<Popover
|
||||
isOpen
|
||||
isDismissable
|
||||
onClose={() => {
|
||||
navigate(absoluteSitePaths.simulateurs.comparaison, {
|
||||
replace: true,
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Documentation
|
||||
engine={autoEntrepreneurEngine}
|
||||
documentationPath="/simulateurs/comparaison-régimes-sociaux/auto-entrepreneur"
|
||||
/>
|
||||
</Popover>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Comparateur
|
|
@ -0,0 +1,543 @@
|
|||
import Engine, { formatValue } from 'publicodes'
|
||||
import { ReactNode } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { DottedName } from '@/../../modele-social'
|
||||
import Value, {
|
||||
WhenApplicable,
|
||||
WhenNotApplicable,
|
||||
} from '@/components/EngineValue'
|
||||
import RuleLink from '@/components/RuleLink'
|
||||
import { HelpIcon } from '@/design-system/icons'
|
||||
import { Grid } from '@/design-system/layout'
|
||||
|
||||
import { BestOption, getBestOption } from '../utils'
|
||||
import StatusCard from './StatusCard'
|
||||
|
||||
const DetailsRowCards = ({
|
||||
engines: [assimiléEngine, autoEntrepreneurEngine, indépendantEngine],
|
||||
dottedName,
|
||||
unit,
|
||||
bestOption,
|
||||
evolutionDottedName,
|
||||
evolutionLabel,
|
||||
footers,
|
||||
label,
|
||||
warnings,
|
||||
}: {
|
||||
engines: [Engine<DottedName>, Engine<DottedName>, Engine<DottedName>]
|
||||
dottedName: DottedName
|
||||
unit?: string
|
||||
bestOption?: 'sasu' | 'ei' | 'ae'
|
||||
evolutionDottedName?: DottedName
|
||||
evolutionLabel?: ReactNode | string
|
||||
footers?: { sasu: ReactNode; ei: ReactNode; ae: ReactNode }
|
||||
label?: ReactNode | string
|
||||
|
||||
warnings?: { sasu?: ReactNode; ei?: ReactNode; ae?: ReactNode }
|
||||
}) => {
|
||||
const assimiléEvaluation = assimiléEngine.evaluate({
|
||||
valeur: dottedName,
|
||||
...(unit && { unité: unit }),
|
||||
})
|
||||
const assimiléValue = formatValue(assimiléEvaluation, {
|
||||
precision: 0,
|
||||
}) as string
|
||||
|
||||
const indépendantEvaluation = indépendantEngine.evaluate({
|
||||
valeur: dottedName,
|
||||
...(unit && { unité: unit }),
|
||||
})
|
||||
const indépendantValue = formatValue(indépendantEvaluation, {
|
||||
precision: 0,
|
||||
}) as string
|
||||
const autoEntrepreneurEvaluation = autoEntrepreneurEngine.evaluate({
|
||||
valeur: dottedName,
|
||||
...(unit && { unité: unit }),
|
||||
})
|
||||
|
||||
const autoEntrepreneurValue = formatValue(autoEntrepreneurEvaluation, {
|
||||
precision: 0,
|
||||
}) as string
|
||||
|
||||
const options: BestOption[] = [
|
||||
{
|
||||
type: 'sasu',
|
||||
value: assimiléEvaluation.nodeValue,
|
||||
},
|
||||
{
|
||||
type: 'ei',
|
||||
value: indépendantEvaluation.nodeValue,
|
||||
},
|
||||
{
|
||||
type: 'ae',
|
||||
value: autoEntrepreneurEvaluation.nodeValue,
|
||||
},
|
||||
]
|
||||
|
||||
const bestOptionValue = bestOption ?? getBestOption(options)
|
||||
|
||||
if (
|
||||
assimiléValue === indépendantValue &&
|
||||
indépendantValue === autoEntrepreneurValue
|
||||
) {
|
||||
return (
|
||||
<Grid container spacing={4}>
|
||||
<Grid item xs={12} lg={12}>
|
||||
<StatusCard
|
||||
status={['sasu', 'ei', 'ae']}
|
||||
footerContent={footers?.sasu}
|
||||
>
|
||||
<WhenNotApplicable dottedName={dottedName} engine={assimiléEngine}>
|
||||
<DisabledLabel>Ne s'applique pas</DisabledLabel>
|
||||
</WhenNotApplicable>
|
||||
<WhenApplicable dottedName={dottedName} engine={assimiléEngine}>
|
||||
<StyledDiv>
|
||||
<span>
|
||||
<Value
|
||||
linkToRule={false}
|
||||
expression={dottedName}
|
||||
engine={assimiléEngine}
|
||||
precision={0}
|
||||
unit={unit}
|
||||
/>
|
||||
{label && ' '}
|
||||
{label && label}
|
||||
</span>
|
||||
<StyledRuleLink
|
||||
documentationPath="/simulateurs/comparaison-régimes-sociaux/SASU"
|
||||
dottedName={dottedName}
|
||||
engine={assimiléEngine}
|
||||
>
|
||||
<HelpIcon />
|
||||
</StyledRuleLink>
|
||||
{warnings?.sasu && warnings?.sasu}
|
||||
</StyledDiv>
|
||||
{evolutionDottedName && (
|
||||
<Precisions>
|
||||
<Value
|
||||
linkToRule={false}
|
||||
expression={evolutionDottedName}
|
||||
engine={assimiléEngine}
|
||||
precision={0}
|
||||
unit={unit}
|
||||
/>{' '}
|
||||
{evolutionLabel}
|
||||
</Precisions>
|
||||
)}
|
||||
{!evolutionDottedName && evolutionLabel && (
|
||||
<Precisions>{evolutionLabel}</Precisions>
|
||||
)}
|
||||
</WhenApplicable>
|
||||
</StatusCard>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
if (assimiléValue === indépendantValue) {
|
||||
return (
|
||||
<Grid container spacing={4}>
|
||||
<Grid item xs={12} lg={8}>
|
||||
<StatusCard
|
||||
status={['sasu', 'ei']}
|
||||
isBestOption={bestOptionValue === 'sasu'}
|
||||
footerContent={footers?.sasu}
|
||||
>
|
||||
<WhenNotApplicable dottedName={dottedName} engine={assimiléEngine}>
|
||||
<DisabledLabel>Ne s'applique pas</DisabledLabel>
|
||||
</WhenNotApplicable>
|
||||
<WhenApplicable dottedName={dottedName} engine={assimiléEngine}>
|
||||
<span>
|
||||
<Value
|
||||
linkToRule={false}
|
||||
expression={dottedName}
|
||||
engine={assimiléEngine}
|
||||
precision={0}
|
||||
unit={unit}
|
||||
/>
|
||||
{label && ' '}
|
||||
{label && label}
|
||||
</span>
|
||||
<StyledRuleLink
|
||||
documentationPath="/simulateurs/comparaison-régimes-sociaux/SASU"
|
||||
dottedName={dottedName}
|
||||
engine={assimiléEngine}
|
||||
>
|
||||
<HelpIcon />
|
||||
</StyledRuleLink>
|
||||
{warnings?.sasu || warnings?.ei
|
||||
? warnings?.sasu
|
||||
? warnings?.sasu
|
||||
: warnings?.ei
|
||||
: ''}
|
||||
{evolutionDottedName && (
|
||||
<Precisions>
|
||||
<Value
|
||||
linkToRule={false}
|
||||
expression={evolutionDottedName}
|
||||
engine={assimiléEngine}
|
||||
precision={0}
|
||||
unit={unit}
|
||||
/>{' '}
|
||||
{evolutionLabel}
|
||||
</Precisions>
|
||||
)}
|
||||
{!evolutionDottedName && evolutionLabel && (
|
||||
<Precisions>{evolutionLabel}</Precisions>
|
||||
)}
|
||||
</WhenApplicable>
|
||||
</StatusCard>
|
||||
</Grid>
|
||||
<Grid item xs={12} lg={4}>
|
||||
<StatusCard
|
||||
status={['ae']}
|
||||
footerContent={footers?.ei}
|
||||
isBestOption={bestOptionValue === 'ae'}
|
||||
>
|
||||
<WhenNotApplicable
|
||||
dottedName={dottedName}
|
||||
engine={autoEntrepreneurEngine}
|
||||
>
|
||||
<DisabledLabel>Ne s'applique pas</DisabledLabel>
|
||||
</WhenNotApplicable>
|
||||
<WhenApplicable
|
||||
dottedName={dottedName}
|
||||
engine={autoEntrepreneurEngine}
|
||||
>
|
||||
<span>
|
||||
<Value
|
||||
linkToRule={false}
|
||||
expression={dottedName}
|
||||
engine={autoEntrepreneurEngine}
|
||||
precision={0}
|
||||
unit={unit}
|
||||
/>
|
||||
{label && ' '}
|
||||
{label && label}
|
||||
</span>
|
||||
<StyledRuleLink
|
||||
documentationPath="/simulateurs/comparaison-régimes-sociaux/auto-entrepreneur"
|
||||
dottedName={dottedName}
|
||||
engine={autoEntrepreneurEngine}
|
||||
>
|
||||
<HelpIcon />
|
||||
</StyledRuleLink>
|
||||
{warnings?.ae && warnings?.ae}
|
||||
{evolutionDottedName && (
|
||||
<Precisions>
|
||||
<Value
|
||||
linkToRule={false}
|
||||
expression={evolutionDottedName}
|
||||
engine={autoEntrepreneurEngine}
|
||||
precision={0}
|
||||
unit={unit}
|
||||
/>{' '}
|
||||
{evolutionLabel}
|
||||
</Precisions>
|
||||
)}
|
||||
{!evolutionDottedName && evolutionLabel && (
|
||||
<Precisions>{evolutionLabel}</Precisions>
|
||||
)}
|
||||
</WhenApplicable>
|
||||
</StatusCard>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
if (indépendantValue === autoEntrepreneurValue) {
|
||||
return (
|
||||
<Grid container spacing={4}>
|
||||
<Grid item xs={12} lg={4}>
|
||||
<StatusCard
|
||||
status={['sasu']}
|
||||
footerContent={footers?.sasu}
|
||||
isBestOption={bestOptionValue === 'sasu'}
|
||||
>
|
||||
<WhenNotApplicable dottedName={dottedName} engine={assimiléEngine}>
|
||||
<DisabledLabel>Ne s'applique pas</DisabledLabel>
|
||||
</WhenNotApplicable>
|
||||
<WhenApplicable dottedName={dottedName} engine={assimiléEngine}>
|
||||
<span>
|
||||
<Value
|
||||
linkToRule={false}
|
||||
expression={dottedName}
|
||||
engine={assimiléEngine}
|
||||
precision={0}
|
||||
unit={unit}
|
||||
/>
|
||||
{label && ' '}
|
||||
{label && label}
|
||||
</span>
|
||||
<StyledRuleLink
|
||||
dottedName={dottedName}
|
||||
engine={assimiléEngine}
|
||||
documentationPath="/simulateurs/comparaison-régimes-sociaux/SASU"
|
||||
>
|
||||
<HelpIcon />
|
||||
</StyledRuleLink>
|
||||
{warnings?.sasu && warnings?.sasu}
|
||||
{evolutionDottedName && (
|
||||
<Precisions>
|
||||
<Value
|
||||
linkToRule={false}
|
||||
expression={evolutionDottedName}
|
||||
engine={assimiléEngine}
|
||||
precision={0}
|
||||
unit={unit}
|
||||
/>{' '}
|
||||
{evolutionLabel}
|
||||
</Precisions>
|
||||
)}
|
||||
{!evolutionDottedName && evolutionLabel && (
|
||||
<Precisions>{evolutionLabel}</Precisions>
|
||||
)}
|
||||
</WhenApplicable>
|
||||
</StatusCard>
|
||||
</Grid>
|
||||
<Grid item xs={12} lg={8}>
|
||||
<StatusCard
|
||||
status={['ei', 'ae']}
|
||||
footerContent={footers?.ei}
|
||||
isBestOption={bestOptionValue === 'ei'}
|
||||
>
|
||||
<WhenNotApplicable
|
||||
dottedName={dottedName}
|
||||
engine={indépendantEngine}
|
||||
>
|
||||
<DisabledLabel>Ne s'applique pas</DisabledLabel>
|
||||
</WhenNotApplicable>
|
||||
<WhenApplicable dottedName={dottedName} engine={indépendantEngine}>
|
||||
<span>
|
||||
<Value
|
||||
linkToRule={false}
|
||||
expression={dottedName}
|
||||
engine={indépendantEngine}
|
||||
precision={0}
|
||||
unit={unit}
|
||||
/>
|
||||
{label && ' '}
|
||||
{label && label}
|
||||
</span>
|
||||
<StyledRuleLink
|
||||
dottedName={dottedName}
|
||||
engine={indépendantEngine}
|
||||
documentationPath="/simulateurs/comparaison-régimes-sociaux/EI"
|
||||
>
|
||||
<HelpIcon />
|
||||
</StyledRuleLink>
|
||||
{warnings?.ei || warnings?.ae
|
||||
? warnings?.ei
|
||||
? warnings?.ei
|
||||
: warnings?.ae
|
||||
: ''}
|
||||
{evolutionDottedName && (
|
||||
<Precisions>
|
||||
<Value
|
||||
linkToRule={false}
|
||||
expression={evolutionDottedName}
|
||||
engine={indépendantEngine}
|
||||
precision={0}
|
||||
unit={unit}
|
||||
/>{' '}
|
||||
{evolutionLabel}
|
||||
</Precisions>
|
||||
)}
|
||||
{!evolutionDottedName && evolutionLabel && (
|
||||
<Precisions>{evolutionLabel}</Precisions>
|
||||
)}
|
||||
</WhenApplicable>
|
||||
</StatusCard>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid container spacing={4}>
|
||||
<Grid item xs={12} lg={4}>
|
||||
<StatusCard
|
||||
status={['sasu']}
|
||||
footerContent={footers?.sasu}
|
||||
isBestOption={bestOptionValue === 'sasu'}
|
||||
>
|
||||
<WhenNotApplicable dottedName={dottedName} engine={assimiléEngine}>
|
||||
<DisabledLabel>Ne s'applique pas</DisabledLabel>
|
||||
</WhenNotApplicable>
|
||||
<WhenApplicable dottedName={dottedName} engine={assimiléEngine}>
|
||||
<span>
|
||||
<Value
|
||||
linkToRule={false}
|
||||
expression={dottedName}
|
||||
engine={assimiléEngine}
|
||||
precision={0}
|
||||
unit={unit}
|
||||
/>
|
||||
{label && ' '}
|
||||
{label && label}
|
||||
</span>
|
||||
<StyledRuleLink
|
||||
dottedName={dottedName}
|
||||
engine={assimiléEngine}
|
||||
documentationPath="/simulateurs/comparaison-régimes-sociaux/SASU"
|
||||
>
|
||||
<HelpIcon />
|
||||
</StyledRuleLink>
|
||||
{warnings?.sasu && warnings?.sasu}
|
||||
{evolutionDottedName && (
|
||||
<Precisions>
|
||||
<Value
|
||||
linkToRule={false}
|
||||
expression={evolutionDottedName}
|
||||
engine={assimiléEngine}
|
||||
precision={0}
|
||||
unit={unit}
|
||||
/>{' '}
|
||||
{evolutionLabel}
|
||||
</Precisions>
|
||||
)}
|
||||
{!evolutionDottedName && evolutionLabel && (
|
||||
<Precisions>{evolutionLabel}</Precisions>
|
||||
)}
|
||||
</WhenApplicable>
|
||||
</StatusCard>
|
||||
</Grid>
|
||||
<Grid item xs={12} lg={4}>
|
||||
<StatusCard
|
||||
status={['ei']}
|
||||
footerContent={footers?.ei}
|
||||
isBestOption={bestOptionValue === 'ei'}
|
||||
>
|
||||
<WhenNotApplicable dottedName={dottedName} engine={indépendantEngine}>
|
||||
<DisabledLabel>Ne s'applique pas</DisabledLabel>
|
||||
</WhenNotApplicable>
|
||||
<WhenApplicable dottedName={dottedName} engine={indépendantEngine}>
|
||||
<span>
|
||||
<Value
|
||||
linkToRule={false}
|
||||
expression={dottedName}
|
||||
engine={indépendantEngine}
|
||||
precision={0}
|
||||
unit={unit}
|
||||
/>
|
||||
{label && ' '}
|
||||
{label && label}
|
||||
</span>
|
||||
<StyledRuleLink
|
||||
dottedName={dottedName}
|
||||
engine={indépendantEngine}
|
||||
documentationPath="/simulateurs/comparaison-régimes-sociaux/EI"
|
||||
>
|
||||
<HelpIcon />
|
||||
</StyledRuleLink>
|
||||
{warnings?.ei && warnings?.ei}
|
||||
{evolutionDottedName && (
|
||||
<Precisions>
|
||||
<Value
|
||||
linkToRule={false}
|
||||
expression={evolutionDottedName}
|
||||
engine={indépendantEngine}
|
||||
precision={0}
|
||||
unit={unit}
|
||||
/>{' '}
|
||||
{evolutionLabel}
|
||||
</Precisions>
|
||||
)}
|
||||
{!evolutionDottedName && evolutionLabel && (
|
||||
<Precisions>{evolutionLabel}</Precisions>
|
||||
)}
|
||||
</WhenApplicable>
|
||||
</StatusCard>
|
||||
</Grid>
|
||||
<Grid item xs={12} lg={4}>
|
||||
<StatusCard
|
||||
status={['ae']}
|
||||
footerContent={footers?.ae}
|
||||
isBestOption={bestOptionValue === 'ae'}
|
||||
>
|
||||
<WhenNotApplicable
|
||||
dottedName={dottedName}
|
||||
engine={autoEntrepreneurEngine}
|
||||
>
|
||||
<DisabledLabel>Ne s'applique pas</DisabledLabel>
|
||||
</WhenNotApplicable>
|
||||
<WhenApplicable
|
||||
dottedName={dottedName}
|
||||
engine={autoEntrepreneurEngine}
|
||||
>
|
||||
<span>
|
||||
<Value
|
||||
linkToRule={false}
|
||||
expression={dottedName}
|
||||
engine={autoEntrepreneurEngine}
|
||||
precision={0}
|
||||
unit={unit}
|
||||
/>
|
||||
{label && ' '}
|
||||
{label && label}
|
||||
</span>
|
||||
<StyledRuleLink
|
||||
dottedName={dottedName}
|
||||
engine={autoEntrepreneurEngine}
|
||||
documentationPath="/simulateurs/comparaison-régimes-sociaux/auto-entrepreneur"
|
||||
>
|
||||
<HelpIcon />
|
||||
</StyledRuleLink>
|
||||
{warnings?.ae && warnings?.ae}
|
||||
{evolutionDottedName && (
|
||||
<Precisions>
|
||||
<Value
|
||||
linkToRule={false}
|
||||
expression={evolutionDottedName}
|
||||
engine={autoEntrepreneurEngine}
|
||||
precision={0}
|
||||
unit={unit}
|
||||
/>{' '}
|
||||
{evolutionLabel}
|
||||
</Precisions>
|
||||
)}
|
||||
{!evolutionDottedName && evolutionLabel && (
|
||||
<Precisions>{evolutionLabel}</Precisions>
|
||||
)}
|
||||
</WhenApplicable>
|
||||
</StatusCard>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
const StyledRuleLink = styled(RuleLink)`
|
||||
display: inline-flex;
|
||||
margin-left: ${({ theme }) => theme.spacings.xxs};
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
`
|
||||
|
||||
const DisabledLabel = styled.span`
|
||||
color: ${({ theme }) => theme.colors.extended.grey[600]}!important;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
font-style: italic;
|
||||
margin: 0 !important;
|
||||
`
|
||||
|
||||
const Precisions = styled.span`
|
||||
display: block;
|
||||
font-family: ${({ theme }) => theme.fonts.main};
|
||||
font-weight: normal;
|
||||
font-size: 1rem;
|
||||
color: ${({ theme }) => theme.colors.extended.grey[700]};
|
||||
margin: 0 !important;
|
||||
margin-top: 0.5rem;
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
export default DetailsRowCards
|
|
@ -0,0 +1,749 @@
|
|||
import { Item } from '@react-stately/collections'
|
||||
import Engine from 'publicodes'
|
||||
import { Trans } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { DottedName } from '@/../../modele-social'
|
||||
import Value, {
|
||||
WhenAlreadyDefined,
|
||||
WhenValueEquals,
|
||||
} from '@/components/EngineValue'
|
||||
import { ExplicableRule } from '@/components/conversation/Explicable'
|
||||
import { Accordion } from '@/design-system'
|
||||
import { Emoji } from '@/design-system/emoji'
|
||||
import { ExternalLinkIcon, PlusCircleIcon } from '@/design-system/icons'
|
||||
import { Container, Grid, Spacing } from '@/design-system/layout'
|
||||
import { Strong } from '@/design-system/typography'
|
||||
import { H2, H4 } from '@/design-system/typography/heading'
|
||||
import { StyledLink } from '@/design-system/typography/link'
|
||||
import { Body } from '@/design-system/typography/paragraphs'
|
||||
|
||||
import DetailsRowCards from './DetailsRowCards'
|
||||
import ItemTitle from './ItemTitle'
|
||||
import StatusCard from './StatusCard'
|
||||
import WarningTooltip from './WarningTooltip'
|
||||
|
||||
const Détails = ({
|
||||
engines: [assimiléEngine, autoEntrepreneurEngine, indépendantEngine],
|
||||
}: {
|
||||
engines: [Engine<DottedName>, Engine<DottedName>, Engine<DottedName>]
|
||||
}) => {
|
||||
return (
|
||||
<StyledContainer
|
||||
backgroundColor={(theme) =>
|
||||
theme.darkMode
|
||||
? theme.colors.extended.dark[800]
|
||||
: theme.colors.bases.primary[200]
|
||||
}
|
||||
>
|
||||
<Accordion
|
||||
variant="light"
|
||||
title={
|
||||
<H2>
|
||||
<Trans>Zoom sur...</Trans>
|
||||
</H2>
|
||||
}
|
||||
isFoldable
|
||||
>
|
||||
<Item
|
||||
title={
|
||||
<ItemTitle>
|
||||
<Trans>La retraite</Trans> <Emoji emoji="🧐" />
|
||||
</ItemTitle>
|
||||
}
|
||||
key="retraite"
|
||||
hasChildItems={false}
|
||||
>
|
||||
<Body>
|
||||
<Trans>
|
||||
Le montant de votre retraite est constitué de{' '}
|
||||
<Strong>
|
||||
votre retraite de base + votre retraite complémentaire
|
||||
</Strong>
|
||||
.
|
||||
</Trans>
|
||||
</Body>
|
||||
<StyledH4>
|
||||
<Trans>Retraite de base</Trans>
|
||||
<ExplicableRule dottedName="protection sociale . retraite . base" />
|
||||
</StyledH4>
|
||||
<Body>
|
||||
<Trans>
|
||||
La pension calculée correspond à celle de{' '}
|
||||
<Strong>vos 25 meilleures années</Strong>, en considérant que vous
|
||||
avez cotisé suffisamment de trimestres (4 trimestres par an) et
|
||||
que vous partez en retraite à l’âge requis pour obtenir un taux
|
||||
plein.
|
||||
</Trans>
|
||||
</Body>
|
||||
|
||||
<DetailsRowCards
|
||||
dottedName="protection sociale . retraite . base"
|
||||
engines={[
|
||||
assimiléEngine,
|
||||
autoEntrepreneurEngine,
|
||||
indépendantEngine,
|
||||
]}
|
||||
unit="€/mois"
|
||||
/>
|
||||
|
||||
<StyledH4>
|
||||
<Trans>Retraite complémentaire</Trans>
|
||||
<ExplicableRule dottedName="protection sociale . retraite . complémentaire" />
|
||||
</StyledH4>
|
||||
<Body>
|
||||
<Trans>
|
||||
Tous les ans, selon votre rémunération,{' '}
|
||||
<Strong>
|
||||
vous gagnez des points qui constituent votre pension de retraite
|
||||
complémentaire
|
||||
</Strong>
|
||||
. En fin de carrière, vos points sont transformés en{' '}
|
||||
<Strong>
|
||||
un montant qui s’ajoute chaque mois à votre retraite de base
|
||||
</Strong>
|
||||
. Cette valeur se calcule sur le long terme. Par exemple, au bout
|
||||
de 10 ans, vous auriez droit à :
|
||||
</Trans>
|
||||
</Body>
|
||||
|
||||
<DetailsRowCards
|
||||
dottedName="protection sociale . retraite . complémentaire"
|
||||
engines={[
|
||||
assimiléEngine,
|
||||
autoEntrepreneurEngine,
|
||||
indépendantEngine,
|
||||
]}
|
||||
unit="€/mois"
|
||||
evolutionLabel={<Trans>au bout de 10 ans</Trans>}
|
||||
/>
|
||||
</Item>
|
||||
<Item
|
||||
title={
|
||||
<ItemTitle>
|
||||
<Trans>La santé</Trans> <Emoji emoji="😷" />
|
||||
</ItemTitle>
|
||||
}
|
||||
key="santé"
|
||||
hasChildItems={false}
|
||||
>
|
||||
<Body
|
||||
css={`
|
||||
margin-bottom: 0;
|
||||
`}
|
||||
>
|
||||
<Trans>
|
||||
Tous les statuts vous ouvrent le droit au{' '}
|
||||
<Strong>remboursement des soins.</Strong>
|
||||
</Trans>
|
||||
</Body>
|
||||
<BodyNoMargin>
|
||||
<Trans>
|
||||
Pour tous les statuts, il est conseillé de souscrire à une{' '}
|
||||
<Strong>prévoyance complémentaire (mutuelle)</Strong> pour
|
||||
améliorer le remboursement des frais de santé.
|
||||
</Trans>
|
||||
</BodyNoMargin>
|
||||
|
||||
<StyledH4>
|
||||
<Trans>Arrêt maladie</Trans>
|
||||
<ExplicableRule dottedName="protection sociale . maladie . arrêt maladie" />
|
||||
</StyledH4>
|
||||
<Body>
|
||||
<Trans>
|
||||
Pour tous les statuts, vous aurez un{' '}
|
||||
<Strong>délai de carence de 3 jours</Strong>. En cas d’arrêt
|
||||
maladie, l’assurance maladie vous versera :
|
||||
</Trans>
|
||||
</Body>
|
||||
<DetailsRowCards
|
||||
dottedName="protection sociale . maladie . arrêt maladie"
|
||||
engines={[
|
||||
assimiléEngine,
|
||||
autoEntrepreneurEngine,
|
||||
indépendantEngine,
|
||||
]}
|
||||
unit="€/jour"
|
||||
warnings={{
|
||||
sasu: (
|
||||
<WhenValueEquals
|
||||
engine={assimiléEngine}
|
||||
expression="protection sociale . maladie . arrêt maladie"
|
||||
value={0}
|
||||
>
|
||||
<WarningTooltip
|
||||
tooltip={
|
||||
<span
|
||||
css={`
|
||||
font-weight: normal;
|
||||
`}
|
||||
>
|
||||
<Trans>
|
||||
Votre <Strong>rémunération</Strong> est{' '}
|
||||
<Strong>trop faible</Strong> pour bénéficier d’arrêt
|
||||
maladie en SASU.
|
||||
</Trans>
|
||||
</span>
|
||||
}
|
||||
id="tooltip-sasu-arrêt-maladie"
|
||||
/>
|
||||
</WhenValueEquals>
|
||||
),
|
||||
}}
|
||||
footers={{
|
||||
sasu: (
|
||||
<StyledDiv>
|
||||
<PlusCircleIcon
|
||||
css={`
|
||||
margin-top: 0 !important;
|
||||
`}
|
||||
/>
|
||||
<Body
|
||||
css={`
|
||||
margin: 0;
|
||||
`}
|
||||
>
|
||||
<Trans>
|
||||
Pour y prétendre, vous devez voir cotisé au moins{' '}
|
||||
<Strong>3 mois</Strong>
|
||||
</Trans>
|
||||
</Body>
|
||||
</StyledDiv>
|
||||
),
|
||||
ei: (
|
||||
<StyledDiv>
|
||||
<PlusCircleIcon
|
||||
css={`
|
||||
margin-top: 0 !important;
|
||||
`}
|
||||
/>
|
||||
<Body
|
||||
css={`
|
||||
margin: 0;
|
||||
`}
|
||||
>
|
||||
<Trans>
|
||||
Pour y prétendre, vous devez voir cotisé au moins{' '}
|
||||
<Strong>12 mois</Strong>
|
||||
</Trans>
|
||||
</Body>
|
||||
</StyledDiv>
|
||||
),
|
||||
ae: (
|
||||
<StyledDiv>
|
||||
<PlusCircleIcon
|
||||
css={`
|
||||
margin-top: 0 !important;
|
||||
`}
|
||||
/>
|
||||
<Body
|
||||
css={`
|
||||
margin: 0;
|
||||
`}
|
||||
>
|
||||
<Trans>
|
||||
Pour y prétendre, vous devez voir cotisé au moins{' '}
|
||||
<Strong>12 mois</Strong>
|
||||
</Trans>
|
||||
</Body>
|
||||
</StyledDiv>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
<StyledH4>
|
||||
<Trans>Accident du travail et maladie professionnelle</Trans>
|
||||
<ExplicableRule dottedName="protection sociale . maladie . accidents du travail et maladies professionnelles . indemmnités" />
|
||||
</StyledH4>
|
||||
<Body>
|
||||
<Trans>
|
||||
En cas d’<Strong>accident de travail</Strong>, de{' '}
|
||||
<Strong>maladie professionnelle</Strong> ou d’un{' '}
|
||||
<Strong>accident sur le trajet domicile-travail</Strong>, vous
|
||||
serez indemnisé(e) à hauteur de :
|
||||
</Trans>
|
||||
</Body>
|
||||
<DetailsRowCards
|
||||
dottedName="protection sociale . maladie . accidents du travail et maladies professionnelles . indemmnités"
|
||||
engines={[
|
||||
assimiléEngine,
|
||||
autoEntrepreneurEngine,
|
||||
indépendantEngine,
|
||||
]}
|
||||
unit="€/mois"
|
||||
evolutionDottedName="protection sociale . maladie . accidents du travail et maladies professionnelles . indemmnités . à partir du 29ème jour"
|
||||
evolutionLabel={<Trans>à partir du 29ème jour</Trans>}
|
||||
/>
|
||||
</Item>
|
||||
<Item
|
||||
title={
|
||||
<ItemTitle>
|
||||
<Trans>La maternité, paternité et adoption</Trans>{' '}
|
||||
<Emoji emoji="🤗" />
|
||||
</ItemTitle>
|
||||
}
|
||||
key="enfants"
|
||||
hasChildItems={false}
|
||||
>
|
||||
<Body
|
||||
css={`
|
||||
margin-bottom: 0;
|
||||
`}
|
||||
>
|
||||
<Trans>
|
||||
Tous les statuts vous ouvrent le droit aux{' '}
|
||||
<Strong>indemnités journalières</Strong> de congé maternité,
|
||||
paternité, adoption.
|
||||
</Trans>
|
||||
</Body>
|
||||
<Body
|
||||
css={`
|
||||
margin-top: 0;
|
||||
`}
|
||||
>
|
||||
<Trans>
|
||||
Pour y prétendre, vous devez avoir cotisé{' '}
|
||||
<Strong>au moins 10 mois</Strong>.
|
||||
</Trans>
|
||||
</Body>
|
||||
|
||||
<DetailsRowCards
|
||||
dottedName="protection sociale . maladie . maternité paternité adoption"
|
||||
engines={[
|
||||
assimiléEngine,
|
||||
autoEntrepreneurEngine,
|
||||
indépendantEngine,
|
||||
]}
|
||||
unit="€/jour"
|
||||
/>
|
||||
|
||||
<StyledH4>
|
||||
<Trans>Maternité</Trans>
|
||||
<ExplicableRule dottedName="protection sociale . maladie . maternité paternité adoption . allocation forfaitaire de repos maternel" />
|
||||
</StyledH4>
|
||||
<Body>
|
||||
<Trans>
|
||||
En plus des indemnités journalières, vous pouvez aussi prétendre à
|
||||
une{' '}
|
||||
<Strong>
|
||||
allocation forfaitaire de repos maternel supplémentaire
|
||||
</Strong>
|
||||
.
|
||||
</Trans>
|
||||
</Body>
|
||||
<DetailsRowCards
|
||||
dottedName="protection sociale . maladie . maternité paternité adoption . allocation forfaitaire de repos maternel"
|
||||
engines={[
|
||||
assimiléEngine,
|
||||
autoEntrepreneurEngine,
|
||||
indépendantEngine,
|
||||
]}
|
||||
label={<Trans>versés en deux fois</Trans>}
|
||||
/>
|
||||
|
||||
<StyledH4>
|
||||
<Trans>Adoption</Trans>
|
||||
<ExplicableRule dottedName="protection sociale . maladie . maternité paternité adoption . allocation forfaitaire de repos adoption" />
|
||||
</StyledH4>
|
||||
<Body>
|
||||
<Trans>
|
||||
En plus des indemnités journalières, vous pouvez aussi prétendre à
|
||||
une{' '}
|
||||
<Strong>
|
||||
allocation forfaitaire de repos parental supplémentaire
|
||||
</Strong>
|
||||
.
|
||||
</Trans>
|
||||
</Body>
|
||||
<DetailsRowCards
|
||||
dottedName="protection sociale . maladie . maternité paternité adoption . allocation forfaitaire de repos adoption"
|
||||
engines={[
|
||||
assimiléEngine,
|
||||
autoEntrepreneurEngine,
|
||||
indépendantEngine,
|
||||
]}
|
||||
label={<Trans>versés en une fois</Trans>}
|
||||
/>
|
||||
</Item>
|
||||
<Item
|
||||
title={
|
||||
<ItemTitle>
|
||||
<Trans>L'invalidité et le décès</Trans> <Emoji emoji="🤕" />
|
||||
</ItemTitle>
|
||||
}
|
||||
key="maladie"
|
||||
hasChildItems={false}
|
||||
>
|
||||
<Body>
|
||||
<Trans>
|
||||
Tous les statuts cotisent pour une{' '}
|
||||
<Strong>pension invalidité-décès</Strong> qui les{' '}
|
||||
<Strong>protège en cas d’invalidité</Strong> et assure à leurs
|
||||
proches une{' '}
|
||||
<Strong>
|
||||
pension de réversion et un capital en cas de décès
|
||||
</Strong>
|
||||
.
|
||||
</Trans>
|
||||
</Body>
|
||||
<StyledH4>
|
||||
<Trans>Invalidité</Trans>
|
||||
<ExplicableRule dottedName="protection sociale . invalidité et décès" />
|
||||
</StyledH4>
|
||||
<BodyNoMargin>
|
||||
<Trans>
|
||||
Vous pouvez bénéficier d’une pension invalidité{' '}
|
||||
<Strong>
|
||||
en cas de maladie ou d’accident conduisant à une incapacité à
|
||||
poursuivre votre activité professionnelle
|
||||
</Strong>
|
||||
.
|
||||
</Trans>
|
||||
</BodyNoMargin>
|
||||
<BodyNoMargin
|
||||
css={`
|
||||
margin-bottom: 1rem;
|
||||
`}
|
||||
>
|
||||
<Trans>
|
||||
Pour y prétendre, vous devez respecter{' '}
|
||||
<BlackColoredLink href="https://www.service-public.fr/particuliers/vosdroits/F672">
|
||||
certaines règles
|
||||
<StyledExternalLinkIcon />
|
||||
</BlackColoredLink>
|
||||
.
|
||||
</Trans>
|
||||
</BodyNoMargin>
|
||||
<DetailsRowCards
|
||||
dottedName="protection sociale . invalidité et décès . pension invalidité . invalidité partielle"
|
||||
evolutionDottedName="protection sociale . invalidité et décès . pension invalidité . invalidité totale"
|
||||
engines={[
|
||||
assimiléEngine,
|
||||
autoEntrepreneurEngine,
|
||||
indépendantEngine,
|
||||
]}
|
||||
unit="€/mois"
|
||||
label={
|
||||
<span style={{ fontSize: '1rem' }}>
|
||||
<Trans>(invalidité partielle)</Trans>
|
||||
</span>
|
||||
}
|
||||
evolutionLabel={
|
||||
<span style={{ fontSize: '0.75rem' }}>
|
||||
<Trans>(invalidité totale)</Trans>
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
<Spacing md />
|
||||
|
||||
<Body
|
||||
css={`
|
||||
margin-top: 2rem;
|
||||
`}
|
||||
>
|
||||
<Trans>
|
||||
Pour une invalidité causée par un accident professionnel, vous
|
||||
pouvez bénéficier d’une <Strong>rente d’incapacité</Strong>.
|
||||
</Trans>
|
||||
</Body>
|
||||
<DetailsRowCards
|
||||
dottedName="protection sociale . invalidité et décès . accidents du travail et maladies professionnelles . rente incapacité"
|
||||
engines={[
|
||||
assimiléEngine,
|
||||
autoEntrepreneurEngine,
|
||||
indépendantEngine,
|
||||
]}
|
||||
unit="€/mois"
|
||||
/>
|
||||
|
||||
<StyledH4>
|
||||
<Trans>Décès</Trans>
|
||||
<ExplicableRule dottedName="protection sociale . invalidité et décès . capital décès" />
|
||||
</StyledH4>
|
||||
<Body>
|
||||
<Trans>
|
||||
La Sécurité Sociale garantit un{' '}
|
||||
<Strong>capital décès pour vos ayants droits</Strong> (personnes
|
||||
qui sont à votre charge) sous certaines conditions.
|
||||
</Trans>
|
||||
</Body>
|
||||
<DetailsRowCards
|
||||
dottedName="protection sociale . invalidité et décès . capital décès"
|
||||
engines={[
|
||||
assimiléEngine,
|
||||
autoEntrepreneurEngine,
|
||||
indépendantEngine,
|
||||
]}
|
||||
/>
|
||||
|
||||
<Body
|
||||
css={`
|
||||
margin-top: 2rem;
|
||||
`}
|
||||
>
|
||||
<Trans>
|
||||
En plus du capital décès, une{' '}
|
||||
<Strong>pension de réversion</Strong> peut être versée au conjoint
|
||||
survivant. Elle correspond aux{' '}
|
||||
<Strong>droits à la retraite acquis par le défunt</Strong> durant
|
||||
sa vie professionnelle.
|
||||
</Trans>
|
||||
</Body>
|
||||
<StatusCard status={['sasu', 'ei', 'ae']}>
|
||||
<span>
|
||||
<Value
|
||||
linkToRule={false}
|
||||
expression="protection sociale . invalidité et décès . pension de reversion"
|
||||
engine={assimiléEngine}
|
||||
precision={0}
|
||||
unit="€/mois"
|
||||
/>{' '}
|
||||
<WhenAlreadyDefined
|
||||
engine={assimiléEngine}
|
||||
dottedName="protection sociale . invalidité et décès . pension de reversion"
|
||||
>
|
||||
<Trans>maximum</Trans>
|
||||
</WhenAlreadyDefined>
|
||||
</span>
|
||||
</StatusCard>
|
||||
|
||||
<Body
|
||||
css={`
|
||||
margin-top: 2rem;
|
||||
`}
|
||||
>
|
||||
<Trans>
|
||||
Pour un décès survenu dans le cadre d’un accident professionnel,
|
||||
vous pouvez bénéficier d’une <Strong>rente de décès</Strong>.
|
||||
</Trans>
|
||||
</Body>
|
||||
|
||||
<DetailsRowCards
|
||||
dottedName="protection sociale . invalidité et décès . accidents du travail et maladies professionnelles . rente décès"
|
||||
engines={[
|
||||
assimiléEngine,
|
||||
autoEntrepreneurEngine,
|
||||
indépendantEngine,
|
||||
]}
|
||||
unit="€/mois"
|
||||
/>
|
||||
|
||||
<Body
|
||||
css={`
|
||||
margin-top: 2rem;
|
||||
`}
|
||||
>
|
||||
<Trans>
|
||||
Un <Strong>capital « orphelin »</Strong> est versé aux enfants des
|
||||
travailleurs indépendants décédés, sous certaines conditions.
|
||||
</Trans>
|
||||
</Body>
|
||||
|
||||
<DetailsRowCards
|
||||
dottedName="protection sociale . invalidité et décès . capital décès . orphelin"
|
||||
engines={[
|
||||
assimiléEngine,
|
||||
autoEntrepreneurEngine,
|
||||
indépendantEngine,
|
||||
]}
|
||||
unit="€/enfant"
|
||||
/>
|
||||
</Item>
|
||||
<Item
|
||||
title={
|
||||
<ItemTitle>
|
||||
<Trans>La gestion juridique et comptable</Trans>{' '}
|
||||
<Emoji emoji="🤓" />
|
||||
</ItemTitle>
|
||||
}
|
||||
key="administratif"
|
||||
hasChildItems={false}
|
||||
>
|
||||
{
|
||||
// TODO : implémenter les valeurs correspondantes dans modèle-social
|
||||
// Ressource : https://entreprendre.service-public.fr/vosdroits/F23282
|
||||
/*
|
||||
<StyledH4>
|
||||
<Trans>Coût de création</Trans>
|
||||
<ExplicableRule dottedName="protection sociale . maladie . arrêt maladie" />
|
||||
</StyledH4>
|
||||
<Body>
|
||||
<Trans>
|
||||
Les formalités de création d'une entreprise diffèrent selon les
|
||||
statuts et la nature de l'activité. Le calcul se concentre ici sur
|
||||
les <Strong>procédures obligatoires</Strong> (immatriculation,
|
||||
annonces légales, rédaction des statuts...).
|
||||
</Trans>
|
||||
</Body>
|
||||
<Grid container spacing={4}>
|
||||
<Grid item xs={12} lg={4}>
|
||||
<StatusCard status={['sasu']}>
|
||||
<span>
|
||||
<Value
|
||||
linkToRule={false}
|
||||
expression="protection sociale . maladie . arrêt maladie"
|
||||
engine={assimiléEngine}
|
||||
precision={0}
|
||||
unit="€"
|
||||
/>
|
||||
</span>
|
||||
<StyledRuleLink
|
||||
dottedName="protection sociale . maladie . arrêt maladie"
|
||||
engine={assimiléEngine}
|
||||
>
|
||||
<HelpIcon />
|
||||
</StyledRuleLink>
|
||||
</StatusCard>
|
||||
</Grid>
|
||||
<Grid item xs={12} lg={4}>
|
||||
<StatusCard status={['ei']}>
|
||||
<span>
|
||||
<Value
|
||||
linkToRule={false}
|
||||
expression="protection sociale . maladie . arrêt maladie"
|
||||
engine={indépendantEngine}
|
||||
precision={0}
|
||||
unit="€"
|
||||
/>
|
||||
</span>
|
||||
<StyledRuleLink
|
||||
dottedName="protection sociale . maladie . arrêt maladie"
|
||||
engine={assimiléEngine}
|
||||
>
|
||||
<HelpIcon />
|
||||
</StyledRuleLink>
|
||||
</StatusCard>
|
||||
</Grid>
|
||||
<Grid item xs={12} lg={4}>
|
||||
<StatusCard status={['ae']}>
|
||||
<Trans>Aucun</Trans>
|
||||
</StatusCard>
|
||||
</Grid>
|
||||
</Grid>
|
||||
*/
|
||||
}
|
||||
|
||||
<StyledH4>
|
||||
<Trans>Dépôt de capital</Trans>
|
||||
<ExplicableRule dottedName="entreprise . capital social" />
|
||||
</StyledH4>
|
||||
<Body>
|
||||
<Trans>
|
||||
Selon les statuts, il est indispensable d’effectuer un{' '}
|
||||
<Strong>apport en capital</Strong> à la création de l’entreprise.
|
||||
Le <Strong>montant minimum</Strong> du capital social est de{' '}
|
||||
<Strong>1 €</Strong>.
|
||||
</Trans>
|
||||
</Body>
|
||||
<Grid container spacing={4}>
|
||||
<Grid item xs={12} lg={4}>
|
||||
<StatusCard status={['sasu']}>
|
||||
<Trans>1 € minimum</Trans>
|
||||
</StatusCard>
|
||||
</Grid>
|
||||
<Grid item xs={12} lg={8}>
|
||||
<StatusCard status={['ei', 'ae']}>
|
||||
<DisabledLabel>
|
||||
<Trans>Aucun</Trans>
|
||||
</DisabledLabel>
|
||||
</StatusCard>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<StyledH4>
|
||||
<Trans>Statut du conjoint</Trans>
|
||||
<ExplicableRule dottedName="protection sociale . maladie . arrêt maladie" />
|
||||
</StyledH4>
|
||||
<Body>
|
||||
<Trans>
|
||||
Vous êtes marié(e), pacsé(e) ou en union libre avec un chef
|
||||
d’entreprise : il existe <Strong>3 statuts possibles</Strong> pour
|
||||
vous (<Strong>conjoint collaborateur</Strong>,{' '}
|
||||
<Strong>conjoint associé</Strong> ou{' '}
|
||||
<Strong>conjoint salarié</Strong>).
|
||||
</Trans>
|
||||
</Body>
|
||||
<Grid container spacing={4}>
|
||||
<Grid item xs={12} lg={4}>
|
||||
<StatusCard status={['sasu']}>
|
||||
<Trans>Conjoint associé ou salarié</Trans>
|
||||
</StatusCard>
|
||||
</Grid>
|
||||
<Grid item xs={12} lg={4}>
|
||||
<StatusCard status={['ei']}>
|
||||
<Trans>Conjoint collaborateur ou salarié</Trans>
|
||||
</StatusCard>
|
||||
</Grid>
|
||||
<Grid item xs={12} lg={4}>
|
||||
<StatusCard status={['ae']}>
|
||||
<Trans>Conjoint collaborateur</Trans>
|
||||
</StatusCard>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Item>
|
||||
</Accordion>
|
||||
</StyledContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const StyledContainer = styled(Container)`
|
||||
padding: ${({ theme }) => theme.spacings.lg};
|
||||
`
|
||||
|
||||
const StyledH4 = styled(H4)`
|
||||
font-size: 1.25rem;
|
||||
color: ${({ theme }) => theme.colors.bases.primary[600]};
|
||||
`
|
||||
// TODO : décommenter une fois l'implémentation du calcul des coûts de créations
|
||||
// ajouté à modèle-social
|
||||
/*
|
||||
const StyledRuleLink = styled(RuleLink)`
|
||||
display: inline-flex;
|
||||
margin-left: ${({ theme }) => theme.spacings.xxs};
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
`
|
||||
const Precisions = styled.span`
|
||||
display: block;
|
||||
font-family: ${({ theme }) => theme.fonts.main};
|
||||
font-weight: normal;
|
||||
font-size: 1rem;
|
||||
color: ${({ theme }) => theme.colors.extended.grey[700]};
|
||||
margin: 0;
|
||||
margin-top: 0.5rem;
|
||||
width: 100%;
|
||||
`
|
||||
*/
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
display: flex;
|
||||
|
||||
svg {
|
||||
width: 2.5rem;
|
||||
margin-right: 1rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
`
|
||||
|
||||
const BodyNoMargin = styled(Body)`
|
||||
margin: 0;
|
||||
`
|
||||
|
||||
const StyledExternalLinkIcon = styled(ExternalLinkIcon)`
|
||||
margin-left: 0.25rem;
|
||||
`
|
||||
|
||||
const BlackColoredLink = styled(StyledLink)`
|
||||
color: ${({ theme }) => theme.colors.extended.grey[800]};
|
||||
`
|
||||
|
||||
const DisabledLabel = styled(Body)`
|
||||
color: ${({ theme }) => theme.colors.extended.grey[600]}!important;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
font-style: italic;
|
||||
margin: 0;
|
||||
`
|
||||
|
||||
export default Détails
|
|
@ -0,0 +1,39 @@
|
|||
import { ReactNode } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { CircledArrowIcon } from '@/design-system/icons'
|
||||
import { H3 } from '@/design-system/typography/heading'
|
||||
|
||||
const ItemTitle = ({ children }: { children: ReactNode }) => {
|
||||
return (
|
||||
<StyledH3>
|
||||
<StyledCircledArrowIcon /> {children}
|
||||
</StyledH3>
|
||||
)
|
||||
}
|
||||
|
||||
const StyledCircledArrowIcon = styled(CircledArrowIcon)`
|
||||
flex-shrink: 0;
|
||||
width: 2.5rem;
|
||||
@media (max-width: ${({ theme }) => theme.breakpointsWidth.md}) {
|
||||
width: 1.5rem;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledH3 = styled(H3)`
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
font-size: 1.625rem;
|
||||
margin: 0;
|
||||
gap: 1rem;
|
||||
text-align: left;
|
||||
@media (max-width: ${({ theme }) => theme.breakpointsWidth.md}) {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
@media (max-width: ${({ theme }) => theme.breakpointsWidth.sm}) {
|
||||
font-size: 1rem;
|
||||
}
|
||||
`
|
||||
|
||||
export default ItemTitle
|
|
@ -0,0 +1,35 @@
|
|||
import { Item } from '@react-stately/collections'
|
||||
import { Trans } from 'react-i18next'
|
||||
|
||||
import { ExplicableRule } from '@/components/conversation/Explicable'
|
||||
import { Emoji } from '@/design-system/emoji'
|
||||
import { Strong } from '@/design-system/typography'
|
||||
import { H4 } from '@/design-system/typography/heading'
|
||||
import { Body } from '@/design-system/typography/paragraphs'
|
||||
|
||||
import ItemTitle from './ItemTitle'
|
||||
|
||||
const RetraiteItem = () => {
|
||||
return (
|
||||
<Item
|
||||
title={
|
||||
<ItemTitle>
|
||||
La retraite <Emoji emoji="🧐" />
|
||||
</ItemTitle>
|
||||
}
|
||||
key="retraite"
|
||||
hasChildItems={false}
|
||||
>
|
||||
<H4>
|
||||
<Trans>Retraite de base</Trans>
|
||||
<ExplicableRule dottedName="protection sociale . retraite . base" />
|
||||
</H4>
|
||||
<Body>
|
||||
Le montant de votre retraite est constitué de{' '}
|
||||
<Strong>votre retraite de base + votre retraite complémentaire</Strong>.
|
||||
</Body>
|
||||
</Item>
|
||||
)
|
||||
}
|
||||
|
||||
export default RetraiteItem
|
|
@ -0,0 +1,317 @@
|
|||
import Engine from 'publicodes'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { DottedName } from '@/../../modele-social'
|
||||
import Value, { Condition, WhenAlreadyDefined } from '@/components/EngineValue'
|
||||
import RuleLink from '@/components/RuleLink'
|
||||
import { CheckList } from '@/design-system'
|
||||
import { ExternalLinkIcon, HelpIcon } from '@/design-system/icons'
|
||||
import { Grid } from '@/design-system/layout'
|
||||
import { H2 } from '@/design-system/typography/heading'
|
||||
import { StyledLink } from '@/design-system/typography/link'
|
||||
import { Body } from '@/design-system/typography/paragraphs'
|
||||
|
||||
import { BestOption, getBestOption } from '../utils'
|
||||
import AllerPlusLoinRevenus from './AllerPlusLoinRevenus'
|
||||
import StatusCard from './StatusCard'
|
||||
import WarningTooltip from './WarningTooltip'
|
||||
|
||||
const RevenuAprèsImpot = ({
|
||||
engines,
|
||||
}: {
|
||||
engines: [Engine<DottedName>, Engine<DottedName>, Engine<DottedName>]
|
||||
}) => {
|
||||
const [assimiléEngine, autoEntrepreneurEngine, indépendantEngine] = engines
|
||||
const { t } = useTranslation()
|
||||
|
||||
const assimiléValue = assimiléEngine.evaluate({
|
||||
valeur: 'dirigeant . rémunération . net . après impôt',
|
||||
unité: '€/mois',
|
||||
}).nodeValue
|
||||
|
||||
const indépendantValue = indépendantEngine.evaluate({
|
||||
valeur: 'dirigeant . rémunération . net . après impôt',
|
||||
unité: '€/mois',
|
||||
}).nodeValue
|
||||
|
||||
const autoEntrepreneurValue = autoEntrepreneurEngine.evaluate({
|
||||
valeur: 'dirigeant . rémunération . net . après impôt',
|
||||
unité: '€/mois',
|
||||
}).nodeValue
|
||||
|
||||
const options: BestOption[] = [
|
||||
{
|
||||
type: 'sasu',
|
||||
value: assimiléValue,
|
||||
},
|
||||
{
|
||||
type: 'ei',
|
||||
value: indépendantValue,
|
||||
},
|
||||
{
|
||||
type: 'ae',
|
||||
value: autoEntrepreneurValue,
|
||||
},
|
||||
]
|
||||
|
||||
const bestOption = getBestOption(options)
|
||||
|
||||
return (
|
||||
<>
|
||||
<H2>
|
||||
<Trans>Revenu après impôt</Trans>
|
||||
</H2>
|
||||
|
||||
<Grid container spacing={4}>
|
||||
<Grid item xs={12} lg={4}>
|
||||
<StatusCard
|
||||
status={['sasu']}
|
||||
isBestOption={bestOption === 'sasu'}
|
||||
footerContent={
|
||||
<CheckList
|
||||
items={[
|
||||
{
|
||||
isChecked: assimiléEngine.evaluate({
|
||||
valeur: 'dirigeant . exonérations . ACRE',
|
||||
}).nodeValue as boolean,
|
||||
label: assimiléEngine.evaluate({
|
||||
valeur: 'dirigeant . exonérations . ACRE',
|
||||
}).nodeValue
|
||||
? t("Tient compte de l'ACRE")
|
||||
: t("Ne prend pas l'ACRE en compte"),
|
||||
},
|
||||
{
|
||||
isChecked: true,
|
||||
label: t("Choix d'imposition : impôt sur les sociétés"),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<span>
|
||||
<Value
|
||||
linkToRule={false}
|
||||
expression="dirigeant . rémunération . net . après impôt"
|
||||
engine={assimiléEngine}
|
||||
precision={0}
|
||||
unit="€/mois"
|
||||
/>{' '}
|
||||
<Condition
|
||||
engine={assimiléEngine}
|
||||
expression="dirigeant . exonérations . ACRE"
|
||||
>
|
||||
<WhenAlreadyDefined
|
||||
dottedName="dirigeant . rémunération . net . après impôt"
|
||||
engine={assimiléEngine}
|
||||
>
|
||||
<Trans>la première année</Trans>
|
||||
</WhenAlreadyDefined>
|
||||
</Condition>
|
||||
</span>
|
||||
<StyledRuleLink
|
||||
dottedName="dirigeant . rémunération . net . après impôt"
|
||||
engine={assimiléEngine}
|
||||
documentationPath="/simulateurs/comparaison-régimes-sociaux/SASU"
|
||||
>
|
||||
<HelpIcon />
|
||||
</StyledRuleLink>
|
||||
</StatusCard>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} lg={4}>
|
||||
<StatusCard
|
||||
status={['ei']}
|
||||
isBestOption={bestOption === 'ei'}
|
||||
footerContent={
|
||||
<CheckList
|
||||
items={[
|
||||
{
|
||||
isChecked: indépendantEngine.evaluate({
|
||||
valeur: 'dirigeant . exonérations . ACRE',
|
||||
}).nodeValue as boolean,
|
||||
label: indépendantEngine.evaluate({
|
||||
valeur: 'dirigeant . exonérations . ACRE',
|
||||
}).nodeValue
|
||||
? t("Tient compte de l'ACRE")
|
||||
: t("Ne prend pas l'ACRE en compte"),
|
||||
},
|
||||
{
|
||||
isChecked: true,
|
||||
label: t(
|
||||
`Choix d'imposition : impôt sur ${
|
||||
indépendantEngine.evaluate({
|
||||
valeur: 'entreprise . imposition',
|
||||
}).nodeValue === 'IS'
|
||||
? 'les sociétés'
|
||||
: 'le revenu'
|
||||
}`
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<span>
|
||||
<Value
|
||||
linkToRule={false}
|
||||
expression="dirigeant . rémunération . net . après impôt"
|
||||
engine={indépendantEngine}
|
||||
precision={0}
|
||||
unit="€/mois"
|
||||
/>{' '}
|
||||
<Condition
|
||||
engine={indépendantEngine}
|
||||
expression="dirigeant . exonérations . ACRE"
|
||||
>
|
||||
<WhenAlreadyDefined
|
||||
dottedName="dirigeant . rémunération . net . après impôt"
|
||||
engine={indépendantEngine}
|
||||
>
|
||||
<Trans>la première année</Trans>
|
||||
</WhenAlreadyDefined>
|
||||
</Condition>
|
||||
</span>
|
||||
<StyledRuleLink
|
||||
dottedName="dirigeant . rémunération . net . après impôt"
|
||||
engine={indépendantEngine}
|
||||
documentationPath="/simulateurs/comparaison-régimes-sociaux/EI"
|
||||
>
|
||||
<HelpIcon />
|
||||
</StyledRuleLink>
|
||||
</StatusCard>{' '}
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} lg={4}>
|
||||
<StatusCard
|
||||
status={['ae']}
|
||||
isBestOption={bestOption === 'ae'}
|
||||
footerContent={
|
||||
<CheckList
|
||||
items={[
|
||||
{
|
||||
isChecked: autoEntrepreneurEngine.evaluate({
|
||||
valeur: 'dirigeant . exonérations . ACRE',
|
||||
}).nodeValue as boolean,
|
||||
label: (
|
||||
<Trans i18nKey="revenu_après_impots.acre">
|
||||
<span>
|
||||
ACRE sous{' '}
|
||||
<BlackColoredLink href="https://www.urssaf.fr/portail/home/independant/je-beneficie-dexonerations/accre.html">
|
||||
certaines conditions
|
||||
<StyledExternalLinkIcon />
|
||||
</BlackColoredLink>
|
||||
</span>
|
||||
</Trans>
|
||||
),
|
||||
},
|
||||
{
|
||||
isChecked: autoEntrepreneurEngine.evaluate({
|
||||
valeur:
|
||||
'dirigeant . auto-entrepreneur . impôt . versement libératoire',
|
||||
}).nodeValue as boolean,
|
||||
label: t("Versement libératoire de l'impôt sur le revenu"),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Value
|
||||
linkToRule={false}
|
||||
expression="dirigeant . rémunération . net . après impôt"
|
||||
engine={autoEntrepreneurEngine}
|
||||
precision={0}
|
||||
unit="€/mois"
|
||||
/>
|
||||
<Condition
|
||||
engine={autoEntrepreneurEngine}
|
||||
expression="dirigeant . exonérations . ACRE"
|
||||
>
|
||||
<WhenAlreadyDefined
|
||||
dottedName="dirigeant . rémunération . net . après impôt"
|
||||
engine={assimiléEngine}
|
||||
>
|
||||
<span
|
||||
css={`
|
||||
margin-left: 0.25rem;
|
||||
`}
|
||||
>
|
||||
<Trans>la première année</Trans>
|
||||
</span>
|
||||
</WhenAlreadyDefined>
|
||||
</Condition>
|
||||
<StyledRuleLink
|
||||
dottedName="dirigeant . rémunération . net . après impôt"
|
||||
engine={autoEntrepreneurEngine}
|
||||
documentationPath="/simulateurs/comparaison-régimes-sociaux/auto-entrepreneur"
|
||||
>
|
||||
<HelpIcon />
|
||||
</StyledRuleLink>
|
||||
<Condition
|
||||
engine={autoEntrepreneurEngine}
|
||||
expression="entreprise . chiffre d'affaires . seuil micro . dépassé"
|
||||
>
|
||||
<WarningTooltip
|
||||
tooltip={
|
||||
<StyledBody id="warning-ae-tooltip">
|
||||
<Trans>
|
||||
Vous allez dépasser le plafond de la micro-entreprise
|
||||
</Trans>{' '}
|
||||
<span>
|
||||
(
|
||||
<Value
|
||||
linkToRule={false}
|
||||
displayedUnit="€"
|
||||
expression={
|
||||
String(
|
||||
autoEntrepreneurEngine.evaluate(
|
||||
'entreprise . activité . nature'
|
||||
).nodeValue
|
||||
) === 'libérale'
|
||||
? "entreprise . chiffre d'affaires . seuil micro . service"
|
||||
: "entreprise . chiffre d'affaires . seuil micro . total"
|
||||
}
|
||||
/>{' '}
|
||||
<Trans>de chiffre d’affaires</Trans>).
|
||||
</span>
|
||||
</StyledBody>
|
||||
}
|
||||
id="tooltip-ae"
|
||||
/>
|
||||
</Condition>
|
||||
</StatusCard>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<DivAlignRight>
|
||||
<AllerPlusLoinRevenus engines={engines} />
|
||||
</DivAlignRight>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default RevenuAprèsImpot
|
||||
|
||||
const StyledRuleLink = styled(RuleLink)`
|
||||
display: inline-flex;
|
||||
margin-left: 0.15rem;
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledExternalLinkIcon = styled(ExternalLinkIcon)`
|
||||
margin-left: 0.25rem;
|
||||
`
|
||||
|
||||
const BlackColoredLink = styled(StyledLink)`
|
||||
color: ${({ theme }) => theme.colors.extended.grey[800]};
|
||||
`
|
||||
|
||||
const DivAlignRight = styled.div`
|
||||
margin-top: ${({ theme }) => theme.spacings.lg};
|
||||
text-align: right;
|
||||
`
|
||||
|
||||
const StyledBody = styled(Body)`
|
||||
color: ${({ theme }) => theme.colors.extended.grey[100]}!important;
|
||||
`
|
|
@ -0,0 +1,122 @@
|
|||
import { Trans } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import Value from '@/components/EngineValue'
|
||||
import { CardContainer } from '@/design-system/card/Card'
|
||||
import { EditIcon } from '@/design-system/icons'
|
||||
import { Grid } from '@/design-system/layout'
|
||||
import { StyledLink } from '@/design-system/typography/link'
|
||||
import { Body } from '@/design-system/typography/paragraphs'
|
||||
import { useGetFullURL } from '@/hooks/useGetFullURL'
|
||||
|
||||
const RevenuEstimé = () => {
|
||||
const fullURL = useGetFullURL()
|
||||
|
||||
return (
|
||||
<CardContainer
|
||||
css={`
|
||||
padding: 1.5rem !important;
|
||||
`}
|
||||
$inert
|
||||
>
|
||||
<Grid container>
|
||||
<Grid
|
||||
css={`
|
||||
padding-right: 1.5rem;
|
||||
`}
|
||||
item
|
||||
xs={12}
|
||||
sm={6}
|
||||
lg={3}
|
||||
>
|
||||
<Label>
|
||||
<Trans>Votre chiffre d'affaires estimé</Trans>
|
||||
</Label>
|
||||
<StyledValue
|
||||
linkToRule={false}
|
||||
expression="entreprise . chiffre d'affaires"
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<StyledGrid item xs={12} sm={6} lg={5}>
|
||||
<Label>
|
||||
<Trans>Vos charges estimées</Trans>
|
||||
</Label>
|
||||
<StyledValue
|
||||
linkToRule={false}
|
||||
unit="€/an"
|
||||
expression="entreprise . charges"
|
||||
/>
|
||||
</StyledGrid>
|
||||
<GridEditLink item xs={12} lg={3}>
|
||||
<StyledA
|
||||
as={StyledLink}
|
||||
href={`${fullURL}#simulation-comparateur`}
|
||||
$noUnderline
|
||||
css={`
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
`}
|
||||
>
|
||||
<StyledEditIcon /> Modifier les informations
|
||||
</StyledA>
|
||||
</GridEditLink>
|
||||
</Grid>
|
||||
</CardContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const Label = styled(Body)`
|
||||
margin: 0;
|
||||
color: ${({ theme }) =>
|
||||
theme.darkMode
|
||||
? theme.colors.extended.grey[200]
|
||||
: theme.colors.extended.grey[600]}!important;
|
||||
font-size: 0.875rem;
|
||||
`
|
||||
|
||||
const StyledValue = styled(Value)`
|
||||
margin: 0;
|
||||
color: ${({ theme }) =>
|
||||
theme.darkMode
|
||||
? theme.colors.extended.grey[100]
|
||||
: theme.colors.bases.primary[700]}!important;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 700;
|
||||
font-family: ${({ theme }) => theme.fonts.main};
|
||||
`
|
||||
|
||||
const StyledGrid = styled(Grid)`
|
||||
border-left: 1px solid ${({ theme }) => theme.colors.extended.grey[400]};
|
||||
padding-left: 1.5rem;
|
||||
|
||||
@media (max-width: ${({ theme }) => theme.breakpointsWidth.sm}) {
|
||||
border-left: none;
|
||||
padding-left: 0;
|
||||
margin-top: ${({ theme }) => theme.spacings.md};
|
||||
}
|
||||
`
|
||||
|
||||
const StyledEditIcon = styled(EditIcon)`
|
||||
margin-right: ${({ theme }) => theme.spacings.xxs};
|
||||
fill: ${({ theme }) =>
|
||||
theme.darkMode
|
||||
? theme.colors.extended.grey[100]
|
||||
: theme.colors.bases.primary[700]}!important;
|
||||
`
|
||||
|
||||
const GridEditLink = styled(Grid)`
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
@media (max-width: ${({ theme }) => theme.breakpointsWidth.lg}) {
|
||||
padding-top: ${({ theme }) => theme.spacings.lg};
|
||||
justify-content: center;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledA = styled.a`
|
||||
text-decoration: none;
|
||||
`
|
||||
|
||||
export default RevenuEstimé
|
|
@ -0,0 +1,33 @@
|
|||
import Engine from 'publicodes'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { DottedName } from '@/../../modele-social'
|
||||
import { Container } from '@/design-system/layout'
|
||||
|
||||
import RevenuAprèsImpot from './RevenuAprèsImpot'
|
||||
import RevenuEstimé from './RevenuEstimé'
|
||||
|
||||
const Résultats = ({
|
||||
engines,
|
||||
}: {
|
||||
engines: [Engine<DottedName>, Engine<DottedName>, Engine<DottedName>]
|
||||
}) => {
|
||||
return (
|
||||
<StyledContainer
|
||||
backgroundColor={(theme) =>
|
||||
theme.darkMode
|
||||
? theme.colors.extended.dark[700]
|
||||
: theme.colors.bases.primary[200]
|
||||
}
|
||||
>
|
||||
<RevenuEstimé />
|
||||
<RevenuAprèsImpot engines={engines} />
|
||||
</StyledContainer>
|
||||
)
|
||||
}
|
||||
|
||||
export default Résultats
|
||||
|
||||
const StyledContainer = styled(Container)`
|
||||
padding: ${({ theme }) => theme.spacings.lg};
|
||||
`
|
|
@ -0,0 +1,160 @@
|
|||
import { ReactNode, useRef } from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { CardContainer } from '@/design-system/card/Card'
|
||||
import { Emoji } from '@/design-system/emoji'
|
||||
import { CircleIcon, HexagonIcon, TriangleIcon } from '@/design-system/icons'
|
||||
import { Grid } from '@/design-system/layout'
|
||||
import { Tag, TagType } from '@/design-system/tag'
|
||||
import { Tooltip } from '@/design-system/tooltip'
|
||||
import { Body } from '@/design-system/typography/paragraphs'
|
||||
import { generateUuid } from '@/utils'
|
||||
|
||||
type StatusCardType = {
|
||||
status: ('sasu' | 'ei' | 'ae')[]
|
||||
footerContent?: ReactNode
|
||||
isBestOption?: boolean
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
const STATUS_DATA = {
|
||||
sasu: {
|
||||
color: 'secondary',
|
||||
label: 'Société (SASU)',
|
||||
},
|
||||
ei: {
|
||||
color: 'independant',
|
||||
label: 'Entreprise individuelle (EI)',
|
||||
},
|
||||
ae: {
|
||||
color: 'tertiary',
|
||||
label: 'Auto-entrepreneur',
|
||||
},
|
||||
}
|
||||
|
||||
const StatusCard = ({
|
||||
status,
|
||||
children,
|
||||
footerContent,
|
||||
isBestOption,
|
||||
}: StatusCardType) => {
|
||||
const tooltipIdRef = useRef(generateUuid())
|
||||
|
||||
return (
|
||||
<StyledCardContainer $inert>
|
||||
<CardBody>
|
||||
<Grid container spacing={1}>
|
||||
{status.map((statusString) => (
|
||||
<Grid item key={statusString}>
|
||||
<StyledTag
|
||||
key={statusString}
|
||||
$color={STATUS_DATA[statusString].color as TagType}
|
||||
$size="sm"
|
||||
>
|
||||
<StatusTagIcon
|
||||
style={{ marginRight: '0.25rem' }}
|
||||
status={statusString}
|
||||
/>
|
||||
{STATUS_DATA[statusString].label}
|
||||
</StyledTag>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
<StyledBody>{children}</StyledBody>
|
||||
</CardBody>
|
||||
{isBestOption && (
|
||||
<Tooltip
|
||||
tooltip={
|
||||
<StyledBodyTooltip
|
||||
css={`
|
||||
font-weight: normal;
|
||||
`}
|
||||
>
|
||||
<Trans>Option la plus avantageuse.</Trans>
|
||||
</StyledBodyTooltip>
|
||||
}
|
||||
id={`tooltip-option-avantageuse-${String(tooltipIdRef.current)}`}
|
||||
>
|
||||
<StyledEmoji
|
||||
emoji="🥇"
|
||||
aria-describedby={`tooltip-option-avantageuse-${String(
|
||||
tooltipIdRef.current
|
||||
)}`}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
{footerContent && <CardFooter>{footerContent}</CardFooter>}
|
||||
</StyledCardContainer>
|
||||
)
|
||||
}
|
||||
|
||||
export default StatusCard
|
||||
|
||||
const StyledCardContainer = styled(CardContainer)`
|
||||
position: relative;
|
||||
align-items: flex-start;
|
||||
padding: 0;
|
||||
`
|
||||
|
||||
const StyledTag = styled(Tag)`
|
||||
display: inline-flex;
|
||||
&:not(:last-child) {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledEmoji = styled(Emoji)`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 1.5rem;
|
||||
font-size: 1.5rem;
|
||||
`
|
||||
|
||||
const StyledBody = styled(Body)`
|
||||
font-size: 1.25rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
margin-top: 0.75rem;
|
||||
`
|
||||
|
||||
const CardBody = styled.div`
|
||||
padding: 1.5rem;
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
const CardFooter = styled.div`
|
||||
width: 100%;
|
||||
border-top: 1px solid ${({ theme }) => theme.colors.extended.grey[300]};
|
||||
padding: 1.5rem;
|
||||
`
|
||||
|
||||
const StyledBodyTooltip = styled(Body)`
|
||||
color: ${({ theme }) => theme.colors.extended.grey[100]}!important;
|
||||
font-size: 0.75rem;
|
||||
margin: 0;
|
||||
`
|
||||
|
||||
export const StatusTagIcon = ({
|
||||
status,
|
||||
...props
|
||||
}: {
|
||||
status: 'sasu' | 'ei' | 'ae'
|
||||
style?: { marginRight: string }
|
||||
}) => {
|
||||
switch (true) {
|
||||
case status.includes('sasu'):
|
||||
return <HexagonIcon {...props} />
|
||||
case status.includes('ei'):
|
||||
return <TriangleIcon {...props} />
|
||||
case status.includes('ae'):
|
||||
return <CircleIcon {...props} />
|
||||
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
import Engine from 'publicodes'
|
||||
import { ComponentProps } from 'react'
|
||||
|
||||
import { DottedName } from '@/../../modele-social'
|
||||
import Value from '@/components/EngineValue'
|
||||
import { H3 } from '@/design-system/typography/heading'
|
||||
|
||||
function TableRow({
|
||||
dottedName,
|
||||
engines: [assimiléEngine, autoEntrepreneurEngine, indépendantEngine],
|
||||
precision,
|
||||
unit,
|
||||
}: {
|
||||
dottedName: DottedName
|
||||
engines: readonly [Engine<DottedName>, Engine<DottedName>, Engine<DottedName>]
|
||||
} & Pick<ComponentProps<typeof Value>, 'precision' | 'unit'>) {
|
||||
return (
|
||||
<>
|
||||
<H3 className="legend">{assimiléEngine.getRule(dottedName).title}</H3>
|
||||
<div className="AS">
|
||||
<Value
|
||||
engine={assimiléEngine}
|
||||
expression={dottedName}
|
||||
documentationPath="/simulateurs/comparaison-régimes-sociaux/SASU"
|
||||
precision={precision}
|
||||
unit={unit}
|
||||
/>
|
||||
</div>
|
||||
<div className="indep">
|
||||
<Value
|
||||
engine={indépendantEngine}
|
||||
expression={dottedName}
|
||||
documentationPath="/simulateurs/comparaison-régimes-sociaux/EI"
|
||||
precision={precision}
|
||||
unit={unit}
|
||||
/>
|
||||
</div>
|
||||
<div className="auto">
|
||||
<Value
|
||||
engine={autoEntrepreneurEngine}
|
||||
expression={dottedName}
|
||||
documentationPath="/simulateurs/comparaison-régimes-sociaux/auto-entrepreneur"
|
||||
precision={precision}
|
||||
unit={unit}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TableRow
|
|
@ -0,0 +1,29 @@
|
|||
import { ReactNode } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { WarningIcon } from '@/design-system/icons'
|
||||
import { Tooltip } from '@/design-system/tooltip'
|
||||
|
||||
const WarningTooltip = ({
|
||||
id,
|
||||
tooltip,
|
||||
}: {
|
||||
id: string
|
||||
tooltip: ReactNode
|
||||
}) => {
|
||||
return (
|
||||
<Tooltip tooltip={tooltip} id={id}>
|
||||
<StyledWarningIcon
|
||||
id={id}
|
||||
aria-label="Attention"
|
||||
aria-describedby={`${id}-description`}
|
||||
/>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
|
||||
export default WarningTooltip
|
||||
|
||||
const StyledWarningIcon = styled(WarningIcon)`
|
||||
margin-left: 0.5rem;
|
||||
`
|
|
@ -0,0 +1,40 @@
|
|||
import {
|
||||
Dispatch,
|
||||
ReactNode,
|
||||
SetStateAction,
|
||||
createContext,
|
||||
useContext,
|
||||
useState,
|
||||
} from 'react'
|
||||
|
||||
type CasParticuliersType = {
|
||||
isAutoEntrepreneurACREEnabled: boolean
|
||||
setIsAutoEntrepreneurACREEnabled: Dispatch<SetStateAction<boolean>>
|
||||
}
|
||||
|
||||
const CasParticuliersContext = createContext<CasParticuliersType>({
|
||||
isAutoEntrepreneurACREEnabled: false,
|
||||
setIsAutoEntrepreneurACREEnabled: () => null,
|
||||
})
|
||||
|
||||
export const CasParticuliersProvider = ({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode
|
||||
}) => {
|
||||
const [isAutoEntrepreneurACREEnabled, setIsAutoEntrepreneurACREEnabled] =
|
||||
useState(false)
|
||||
|
||||
return (
|
||||
<CasParticuliersContext.Provider
|
||||
value={{
|
||||
isAutoEntrepreneurACREEnabled,
|
||||
setIsAutoEntrepreneurACREEnabled,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</CasParticuliersContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useCasParticuliers = () => useContext(CasParticuliersContext)
|
|
@ -0,0 +1,99 @@
|
|||
import { DottedName } from 'modele-social'
|
||||
import Engine from 'publicodes'
|
||||
import { useMemo } from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
|
||||
import { useEngine, useRawSituation } from '@/components/utils/EngineContext'
|
||||
import useSimulationConfig from '@/components/utils/useSimulationConfig'
|
||||
import { Strong } from '@/design-system/typography'
|
||||
import { Intro } from '@/design-system/typography/paragraphs'
|
||||
import { useSitePaths } from '@/sitePaths'
|
||||
|
||||
import { configComparateurStatuts } from '../configs/comparateurStatuts'
|
||||
import Comparateur from './components/Comparateur'
|
||||
import {
|
||||
CasParticuliersProvider,
|
||||
useCasParticuliers,
|
||||
} from './contexts/CasParticuliers'
|
||||
|
||||
function ComparateurStatutsUI() {
|
||||
const engine = useEngine()
|
||||
const situation = useRawSituation()
|
||||
|
||||
const { isAutoEntrepreneurACREEnabled } = useCasParticuliers()
|
||||
|
||||
const { absoluteSitePaths } = useSitePaths()
|
||||
useSimulationConfig({
|
||||
path: absoluteSitePaths.simulateurs.comparaison,
|
||||
config: configComparateurStatuts,
|
||||
})
|
||||
|
||||
const assimiléEngine = useMemo(
|
||||
() =>
|
||||
engine.shallowCopy().setSituation({
|
||||
...situation,
|
||||
'entreprise . imposition': "'IS'",
|
||||
'entreprise . catégorie juridique': "'SAS'",
|
||||
'entreprise . catégorie juridique . SAS . unipersonnelle': 'oui',
|
||||
}),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[situation]
|
||||
)
|
||||
const autoEntrepreneurEngine = useMemo(
|
||||
() =>
|
||||
engine.shallowCopy().setSituation({
|
||||
...situation,
|
||||
'entreprise . catégorie juridique': "'EI'",
|
||||
'entreprise . catégorie juridique . EI . auto-entrepreneur': 'oui',
|
||||
...(isAutoEntrepreneurACREEnabled
|
||||
? { 'dirigeant . exonérations . ACRE': 'oui' }
|
||||
: { 'dirigeant . exonérations . ACRE': 'non' }),
|
||||
}),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[situation, isAutoEntrepreneurACREEnabled]
|
||||
)
|
||||
|
||||
const indépendantEngine = useMemo(
|
||||
() =>
|
||||
engine.shallowCopy().setSituation({
|
||||
...situation,
|
||||
'entreprise . imposition':
|
||||
situation['entreprise . imposition'] ?? "'IS'",
|
||||
'entreprise . catégorie juridique': "'EI'",
|
||||
'entreprise . catégorie juridique . EI . auto-entrepreneur': 'non',
|
||||
}),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[situation]
|
||||
)
|
||||
|
||||
const engines = [
|
||||
assimiléEngine,
|
||||
autoEntrepreneurEngine,
|
||||
indépendantEngine,
|
||||
] as [Engine<DottedName>, Engine<DottedName>, Engine<DottedName>]
|
||||
|
||||
return (
|
||||
<>
|
||||
<Intro>
|
||||
<Trans i18nKey="comparaisonRégimes.description">
|
||||
Lorsque vous créez votre société, le choix du statut juridique va{' '}
|
||||
<Strong>
|
||||
déterminer à quel régime social le dirigeant est affilié
|
||||
</Strong>
|
||||
. Il en existe <Strong>trois différents</Strong>, avec chacun ses
|
||||
avantages et inconvénients. Avec ce comparatif, trouvez celui qui vous
|
||||
correspond le mieux.
|
||||
</Trans>
|
||||
</Intro>
|
||||
<Comparateur engines={engines} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default function ComparateurStatuts() {
|
||||
return (
|
||||
<CasParticuliersProvider>
|
||||
<ComparateurStatutsUI />
|
||||
</CasParticuliersProvider>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
export type ValueType =
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null
|
||||
| Record<string, unknown>
|
||||
export type BestOption = {
|
||||
type: 'sasu' | 'ei' | 'ae'
|
||||
value?: ValueType
|
||||
}
|
||||
export const getBestOption = (options: BestOption[]) => {
|
||||
const sortedOptions = options.sort(
|
||||
(option1: BestOption, option2: BestOption) => {
|
||||
if (option1.value === null || option1.value === undefined) {
|
||||
return 1
|
||||
}
|
||||
if (option2.value === null || option2.value === undefined) {
|
||||
return -1
|
||||
}
|
||||
|
||||
if (option1.value === option2.value) return 0
|
||||
// console.log(option1.value, option2.value, option1.value > option2.value)
|
||||
|
||||
return option1.value > option2.value ? -1 : 1
|
||||
}
|
||||
)
|
||||
|
||||
return sortedOptions?.[0]?.type
|
||||
}
|
|
@ -4,7 +4,7 @@ import { Trans, useTranslation } from 'react-i18next'
|
|||
import styled from 'styled-components'
|
||||
|
||||
import { Button } from '@/design-system/buttons'
|
||||
import HelpButton from '@/design-system/buttons/HelpButton'
|
||||
import HelpButtonWithPopover from '@/design-system/buttons/HelpButtonWithPopover'
|
||||
import { CardContainer } from '@/design-system/card/Card'
|
||||
import { Emoji } from '@/design-system/emoji'
|
||||
import { Checkbox } from '@/design-system/field'
|
||||
|
@ -88,9 +88,9 @@ export const ActiviteCard = ({
|
|||
)}
|
||||
<ActiviteContent>
|
||||
<H4 as="h3">{titre}</H4>
|
||||
<HelpButton title={titre} type="aide">
|
||||
<HelpButtonWithPopover title={titre} type="aide">
|
||||
<Body>{explication}</Body>
|
||||
</HelpButton>
|
||||
</HelpButtonWithPopover>
|
||||
<SmallBody
|
||||
css={`
|
||||
flex: 1;
|
||||
|
|
|
@ -1,235 +0,0 @@
|
|||
import { DottedName } from 'modele-social'
|
||||
import Engine from 'publicodes'
|
||||
import { ComponentProps, useMemo } from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { Route, Routes } from 'react-router-dom'
|
||||
|
||||
import Value from '@/components/EngineValue'
|
||||
import PeriodSwitch from '@/components/PeriodSwitch'
|
||||
import { StyledGrid } from '@/components/SchemeComparaison'
|
||||
import Simulation, {
|
||||
SimulationGoal,
|
||||
SimulationGoals,
|
||||
} from '@/components/Simulation'
|
||||
import { useEngine, useRawSituation } from '@/components/utils/EngineContext'
|
||||
import useSimulationConfig from '@/components/utils/useSimulationConfig'
|
||||
import { Emoji } from '@/design-system/emoji'
|
||||
import { Spacing } from '@/design-system/layout'
|
||||
import { H2, H3 } from '@/design-system/typography/heading'
|
||||
import { Intro } from '@/design-system/typography/paragraphs'
|
||||
import { useSitePaths } from '@/sitePaths'
|
||||
|
||||
import Documentation from '../Documentation'
|
||||
import { configComparateurStatuts } from './configs/comparateurStatuts'
|
||||
|
||||
export default function SchemeComparaisonPage() {
|
||||
const engine = useEngine()
|
||||
const situation = useRawSituation()
|
||||
|
||||
const { absoluteSitePaths } = useSitePaths()
|
||||
useSimulationConfig({
|
||||
path: absoluteSitePaths.simulateurs.comparaison,
|
||||
config: configComparateurStatuts,
|
||||
})
|
||||
|
||||
const assimiléEngine = useMemo(
|
||||
() =>
|
||||
engine.shallowCopy().setSituation({
|
||||
...situation,
|
||||
'entreprise . catégorie juridique': "'SAS'",
|
||||
'entreprise . catégorie juridique . SAS . unipersonnelle': 'oui',
|
||||
}),
|
||||
[situation]
|
||||
)
|
||||
const autoEntrepreneurEngine = useMemo(
|
||||
() =>
|
||||
engine.shallowCopy().setSituation({
|
||||
...situation,
|
||||
'entreprise . catégorie juridique': "'EI'",
|
||||
'entreprise . catégorie juridique . EI . auto-entrepreneur': 'oui',
|
||||
}),
|
||||
[situation]
|
||||
)
|
||||
const indépendantEngine = useMemo(
|
||||
() =>
|
||||
engine.shallowCopy().setSituation({
|
||||
...situation,
|
||||
'entreprise . catégorie juridique': "'EI'",
|
||||
'entreprise . catégorie juridique . EI . auto-entrepreneur': 'non',
|
||||
}),
|
||||
[situation]
|
||||
)
|
||||
|
||||
const engines = [
|
||||
assimiléEngine,
|
||||
autoEntrepreneurEngine,
|
||||
indépendantEngine,
|
||||
] as [Engine<DottedName>, Engine<DottedName>, Engine<DottedName>]
|
||||
|
||||
return (
|
||||
<Routes>
|
||||
<Route
|
||||
path="SASU/*"
|
||||
element={
|
||||
<>
|
||||
<Documentation
|
||||
engine={assimiléEngine}
|
||||
documentationPath="/simulateurs/comparaison-régimes-sociaux/SASU"
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="EI/*"
|
||||
element={
|
||||
<Documentation
|
||||
engine={indépendantEngine}
|
||||
documentationPath="/simulateurs/comparaison-régimes-sociaux/EI"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="auto-entrepreneur/*"
|
||||
element={
|
||||
<Documentation
|
||||
engine={autoEntrepreneurEngine}
|
||||
documentationPath="/simulateurs/comparaison-régimes-sociaux/auto-entrepreneur"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path=""
|
||||
element={
|
||||
<>
|
||||
<Intro>
|
||||
<Trans i18nKey="comparaisonRégimes.description">
|
||||
Lorsque vous créez votre société, le choix du statut juridique
|
||||
va déterminer à quel régime social le dirigeant est affilié. Il
|
||||
en existe trois différents, avec chacun ses avantages et
|
||||
inconvénients. Avec ce comparatif, trouvez celui qui vous
|
||||
correspond le mieux.
|
||||
</Trans>
|
||||
</Intro>
|
||||
<Comparateur engines={engines} />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
)
|
||||
}
|
||||
|
||||
type ComparateurProps = {
|
||||
engines: [Engine<DottedName>, Engine<DottedName>, Engine<DottedName>]
|
||||
}
|
||||
|
||||
function Comparateur({ engines }: ComparateurProps) {
|
||||
return (
|
||||
<>
|
||||
<Simulation engines={engines} hideDetails showQuestionsFromBeginning>
|
||||
<SimulationGoals
|
||||
toggles={<PeriodSwitch />}
|
||||
legend={'Estimations sur votre rémunération brute et vos charges'}
|
||||
>
|
||||
<SimulationGoal dottedName="entreprise . chiffre d'affaires" />
|
||||
<SimulationGoal dottedName="entreprise . charges" />
|
||||
</SimulationGoals>
|
||||
</Simulation>
|
||||
<Spacing md />
|
||||
<StyledGrid>
|
||||
<H3 className="AS">
|
||||
<Emoji emoji="☂" /> <Trans>SASU</Trans>
|
||||
</H3>
|
||||
<H3 className="indep">
|
||||
<Emoji emoji="👩🔧" /> <Trans>EI / EURL</Trans>
|
||||
</H3>
|
||||
<H3 className="auto">
|
||||
<Emoji emoji="🚶♂️" /> <Trans>Auto-entrepreneur</Trans>
|
||||
</H3>
|
||||
|
||||
<TableRow
|
||||
dottedName="dirigeant . rémunération . net . après impôt"
|
||||
unit="€/mois"
|
||||
precision={0}
|
||||
engines={engines}
|
||||
/>
|
||||
|
||||
<H2 className="all">
|
||||
<Spacing lg /> Retraite
|
||||
</H2>
|
||||
|
||||
<TableRow
|
||||
dottedName="protection sociale . retraite . trimestres"
|
||||
engines={engines}
|
||||
/>
|
||||
<TableRow
|
||||
dottedName="protection sociale . retraite . base"
|
||||
engines={engines}
|
||||
/>
|
||||
<TableRow
|
||||
dottedName="protection sociale . retraite . complémentaire"
|
||||
engines={engines}
|
||||
/>
|
||||
|
||||
<H2 className="all">
|
||||
<Spacing lg /> Santé
|
||||
</H2>
|
||||
<TableRow
|
||||
dottedName="protection sociale . maladie . arrêt maladie"
|
||||
precision={0}
|
||||
engines={engines}
|
||||
/>
|
||||
<TableRow
|
||||
dottedName="protection sociale . maladie . arrêt maladie . délai d'attente"
|
||||
engines={engines}
|
||||
/>
|
||||
<TableRow
|
||||
dottedName="protection sociale . maladie . arrêt maladie . délai de carence"
|
||||
engines={engines}
|
||||
/>
|
||||
</StyledGrid>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function TableRow({
|
||||
dottedName,
|
||||
engines: [assimiléEngine, autoEntrepreneurEngine, indépendantEngine],
|
||||
precision,
|
||||
unit,
|
||||
}: {
|
||||
dottedName: DottedName
|
||||
engines: readonly [Engine<DottedName>, Engine<DottedName>, Engine<DottedName>]
|
||||
} & Pick<ComponentProps<typeof Value>, 'precision' | 'unit'>) {
|
||||
return (
|
||||
<>
|
||||
<H3 className="legend">{assimiléEngine.getRule(dottedName).title}</H3>
|
||||
<div className="AS">
|
||||
<Value
|
||||
engine={assimiléEngine}
|
||||
expression={dottedName}
|
||||
documentationPath="/simulateurs/comparaison-régimes-sociaux/SASU"
|
||||
precision={precision}
|
||||
unit={unit}
|
||||
/>
|
||||
</div>
|
||||
<div className="indep">
|
||||
<Value
|
||||
engine={indépendantEngine}
|
||||
expression={dottedName}
|
||||
documentationPath="/simulateurs/comparaison-régimes-sociaux/EI"
|
||||
precision={precision}
|
||||
unit={unit}
|
||||
/>
|
||||
</div>
|
||||
<div className="auto">
|
||||
<Value
|
||||
engine={autoEntrepreneurEngine}
|
||||
expression={dottedName}
|
||||
documentationPath="/simulateurs/comparaison-régimes-sociaux/auto-entrepreneur"
|
||||
precision={precision}
|
||||
unit={unit}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -38,5 +38,6 @@ export const configComparateurStatuts: SimulationConfig = {
|
|||
"entreprise . chiffre d'affaires": '4000 €/mois',
|
||||
'entreprise . charges': '1000 €/mois',
|
||||
'entreprise . date de création': "période . début d'année",
|
||||
'dirigeant . exonérations . ACRE': 'non',
|
||||
},
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import FormulaireMobilitéIndépendant from '../gerer/demande-mobilité'
|
|||
import ArtisteAuteur from './ArtisteAuteur'
|
||||
import AutoEntrepreneur from './AutoEntrepreneur'
|
||||
import ChômagePartielComponent from './ChômagePartiel'
|
||||
import SchemeComparaisonPage from './ComparateurStatuts'
|
||||
import DividendesSimulation from './Dividendes'
|
||||
import ÉconomieCollaborative from './EconomieCollaborative'
|
||||
import ExonérationCovid from './ExonerationCovid'
|
||||
|
@ -32,7 +33,6 @@ import IndépendantSimulation, {
|
|||
import PAMCHome from './PAMCHome'
|
||||
import { SASUSimulation } from './SASU'
|
||||
import SalariéSimulation from './Salarié'
|
||||
import SchemeComparaisonPage from './SchemeComparaison'
|
||||
import { configAutoEntrepreneur } from './configs/autoEntrepreneur'
|
||||
import { configChômagePartiel } from './configs/chômagePartiel'
|
||||
import { configSASU } from './configs/dirigeantSASU'
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Trans } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import HelpButton from '@/design-system/buttons/HelpButton'
|
||||
import HelpButtonWithPopover from '@/design-system/buttons/HelpButtonWithPopover'
|
||||
import { Li, Ul } from '@/design-system/typography/list'
|
||||
import { Body, baseParagraphStyle } from '@/design-system/typography/paragraphs'
|
||||
|
||||
|
@ -36,7 +36,11 @@ const suramortissementHeader = 'suramortissementHeader'
|
|||
|
||||
export function ExplicationsResultatFiscal() {
|
||||
return (
|
||||
<HelpButton title="Quelles exonérations inclure ?" type="aide" bigPopover>
|
||||
<HelpButtonWithPopover
|
||||
title="Quelles exonérations inclure ?"
|
||||
type="aide"
|
||||
bigPopover
|
||||
>
|
||||
<Body>
|
||||
Pour calculer le montant du résultat fiscal avant déduction des
|
||||
exonérations et des charges sociales à indiquer dans ce simulateur, vous
|
||||
|
@ -207,6 +211,6 @@ export function ExplicationsResultatFiscal() {
|
|||
</tr>
|
||||
</tbody>
|
||||
</StyledTable>
|
||||
</HelpButton>
|
||||
</HelpButtonWithPopover>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -240,3 +240,7 @@ export const catchDivideByZeroError = <T>(func: () => T) => {
|
|||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
export const generateUuid = () => {
|
||||
return Math.floor(Math.random() * Date.now()).toString(16)
|
||||
}
|
||||
|
|
32
yarn.lock
32
yarn.lock
|
@ -3188,6 +3188,22 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@floating-ui/core@npm:^1.0.5":
|
||||
version: 1.1.0
|
||||
resolution: "@floating-ui/core@npm:1.1.0"
|
||||
checksum: ac48969915247320e52d173480c224e2ded94d557ba4cc504547bb314d126348dcc0aeef05686673e1b289596e6ce15118edc84900dd310c613d805f83b4e27d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@floating-ui/dom@npm:^1.0.4":
|
||||
version: 1.1.0
|
||||
resolution: "@floating-ui/dom@npm:1.1.0"
|
||||
dependencies:
|
||||
"@floating-ui/core": ^1.0.5
|
||||
checksum: 717551da6f470101cd1de0edc449b229fade7f94c2ff98d09e14ced041e27092aac94bd78756c4247a42b57129f187292f145f0001a81ece399a89b20b4be60b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/ecma402-abstract@npm:1.13.0":
|
||||
version: 1.13.0
|
||||
resolution: "@formatjs/ecma402-abstract@npm:1.13.0"
|
||||
|
@ -11905,7 +11921,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"classnames@npm:^2.2.5":
|
||||
"classnames@npm:^2.2.5, classnames@npm:^2.3.2":
|
||||
version: 2.3.2
|
||||
resolution: "classnames@npm:2.3.2"
|
||||
checksum: 2c62199789618d95545c872787137262e741f9db13328e216b093eea91c85ef2bfb152c1f9e63027204e2559a006a92eb74147d46c800a9f96297ae1d9f96f4e
|
||||
|
@ -24676,6 +24692,19 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-tooltip@npm:^5.4.0":
|
||||
version: 5.4.0
|
||||
resolution: "react-tooltip@npm:5.4.0"
|
||||
dependencies:
|
||||
"@floating-ui/dom": ^1.0.4
|
||||
classnames: ^2.3.2
|
||||
peerDependencies:
|
||||
react: ">=18.0.0"
|
||||
react-dom: ">=18.0.0"
|
||||
checksum: d6947e849a2d89ae9dca6211b9750217ffcd76778a9a66dbfaf745420d1412512a86343a36c81136d3122b368f90899a150fcc94107f9e68ba89ffbe8b426e6e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-transition-group@npm:2.9.0":
|
||||
version: 2.9.0
|
||||
resolution: "react-transition-group@npm:2.9.0"
|
||||
|
@ -26231,6 +26260,7 @@ __metadata:
|
|||
react-router-dom: ^6.4.4
|
||||
react-signature-pad-wrapper: ^3.3.1
|
||||
react-spring: ^9.5.5
|
||||
react-tooltip: ^5.4.0
|
||||
react-use-measure: ^2.1.1
|
||||
recharts: 2.3.2
|
||||
reduce-reducers: ^1.0.4
|
||||
|
|
Loading…
Reference in New Issue