feat: ajout de champs heures sup sur le simulateur RGCP
parent
3faa153250
commit
9e307ebd36
|
@ -142,7 +142,7 @@ export default function NumberField(props: NumberFieldProps) {
|
|||
</StyledUnit>
|
||||
)}
|
||||
|
||||
{props.label && (
|
||||
{props.label && !props.small && (
|
||||
<StyledLabel {...labelProps}>{props.label}</StyledLabel>
|
||||
)}
|
||||
</StyledInputContainer>
|
||||
|
|
|
@ -22,6 +22,11 @@ export const FocusStyle = css`
|
|||
box-shadow: 0 0 0 2px #ffffff;
|
||||
`
|
||||
|
||||
export const FlexCenter = css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
export const GlobalStyle = createGlobalStyle`
|
||||
html {
|
||||
transition: none !important;
|
||||
|
|
|
@ -89,6 +89,7 @@ Décrivez votre projet ou votre problème en donnant quelques éléments de cont
|
|||
the right advisor for your request.
|
||||
He or she will contact you by telephone within 5 days, and will provide you with assistance tailored to your situation.
|
||||
Décès: Deaths
|
||||
Déplier: Unfold
|
||||
"Détail du montant :": "Amount in detail :"
|
||||
Effacer mes réponses: Delete my answers
|
||||
Effectif de l'entreprise: Number of employees
|
||||
|
@ -208,6 +209,7 @@ Nouveau contenu disponible, cliquez sur recharger pour mettre à jour la page.:
|
|||
Nouveautés: News
|
||||
Option ACRE non activée: ACRE option not activated
|
||||
Option la plus avantageuse.: Most advantageous option.
|
||||
Options: Options
|
||||
Oui: Yes
|
||||
Outils pour les développeurs: Tools for developers
|
||||
PFU (<1>"flat tax"</1>): PFU (<1>"flat tax"</1>)
|
||||
|
@ -1487,6 +1489,16 @@ pages:
|
|||
title: General reduction
|
||||
month-by-month:
|
||||
caption: "General discount month by month :"
|
||||
options:
|
||||
description: Adds fields to modulate employee activity
|
||||
label:
|
||||
heures-complémentaires: Overtime
|
||||
heures-supplémentaires: Overtime
|
||||
popover: "<0>The number of hours of overtime or complementary work is used to
|
||||
calculate the general reduction: gross remuneration is compared with
|
||||
the SMIC increased by this number of hours.</0><1>If you have answered
|
||||
the question on overtime or complementary hours, the value will be
|
||||
overwritten by the value you enter month by month.</1>"
|
||||
régularisation:
|
||||
annuelle: Annual adjustment
|
||||
progressive: Progressive regularization
|
||||
|
|
|
@ -95,6 +95,7 @@ Décrivez votre projet ou votre problème en donnant quelques éléments de cont
|
|||
le conseiller compétent pour votre demande.
|
||||
Celui-ci vous contactera par téléphone sous 5 jours et vous accompagnera en fonction de votre situation.
|
||||
Décès: Décès
|
||||
Déplier: Déplier
|
||||
"Détail du montant :": "Détail du montant :"
|
||||
Effacer mes réponses: Effacer mes réponses
|
||||
Effectif de l'entreprise: Effectif de l'entreprise
|
||||
|
@ -220,6 +221,7 @@ Nouveau contenu disponible, cliquez sur recharger pour mettre à jour la page.:
|
|||
Nouveautés: Nouveautés
|
||||
Option ACRE non activée: Option ACRE non activée
|
||||
Option la plus avantageuse.: Option la plus avantageuse.
|
||||
Options: Options
|
||||
Oui: Oui
|
||||
Outils pour les développeurs: Outils pour les développeurs
|
||||
PFU (<1>"flat tax"</1>): PFU (<1>"flat tax"</1>)
|
||||
|
@ -1581,6 +1583,17 @@ pages:
|
|||
title: Réduction générale
|
||||
month-by-month:
|
||||
caption: "Réduction générale mois par mois :"
|
||||
options:
|
||||
description: Ajoute des champs pour moduler l'activité du salarié
|
||||
label:
|
||||
heures-complémentaires: Heures complémentaires
|
||||
heures-supplémentaires: Heures supplémentaires
|
||||
popover: "<0>Le nombre d'heures supplémentaires et complémentaires est utilisé
|
||||
dans le calcul de la réduction générale : la rémunération brute est
|
||||
comparée au montant du SMIC majoré de ce nombre d'heures.</0><1>Si
|
||||
vous avez répondu à la question sur les heures supplémentaires ou
|
||||
complémentaires, la valeur sera écrasée par celle que vous saisissez
|
||||
mois par mois.</1>"
|
||||
régularisation:
|
||||
annuelle: Régularisation annuelle
|
||||
progressive: Régularisation progressive
|
||||
|
|
|
@ -31,6 +31,7 @@ import RéductionGénéraleMoisParMois from './RéductionGénéraleMoisParMois'
|
|||
import {
|
||||
getInitialRéductionGénéraleMoisParMois,
|
||||
MonthState,
|
||||
Options,
|
||||
réductionGénéraleDottedName,
|
||||
reevaluateRéductionGénéraleMoisParMois,
|
||||
RégularisationMethod,
|
||||
|
@ -125,7 +126,7 @@ function RéductionGénéraleSimulationGoals({
|
|||
}
|
||||
}, [
|
||||
initializeRéductionGénéraleMoisParMoisData,
|
||||
réductionGénéraleMoisParMoisData,
|
||||
réductionGénéraleMoisParMoisData.length,
|
||||
])
|
||||
|
||||
const situation = useSelector(situationSelector)
|
||||
|
@ -178,12 +179,30 @@ function RéductionGénéraleSimulationGoals({
|
|||
})
|
||||
}
|
||||
|
||||
const onOptionsChange = (monthIndex: number, options: Options) => {
|
||||
setData((previousData) => {
|
||||
const updatedData = [...previousData]
|
||||
updatedData[monthIndex] = {
|
||||
...updatedData[monthIndex],
|
||||
options,
|
||||
}
|
||||
|
||||
return reevaluateRéductionGénéraleMoisParMois(
|
||||
updatedData,
|
||||
engine,
|
||||
year,
|
||||
régularisationMethod
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<SimulationGoals toggles={toggles} legend={legend}>
|
||||
{monthByMonth ? (
|
||||
<RéductionGénéraleMoisParMois
|
||||
data={réductionGénéraleMoisParMoisData}
|
||||
onChange={onRémunérationChange}
|
||||
onRémunérationChange={onRémunérationChange}
|
||||
onOptionsChange={onOptionsChange}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
|
|
|
@ -5,16 +5,18 @@ import { ExplicableRule } from '@/components/conversation/Explicable'
|
|||
|
||||
import RéductionGénéraleMoisParMoisRow from './components/RéductionGénéraleMoisParMoisRow'
|
||||
import Warnings from './components/Warnings'
|
||||
import { MonthState, réductionGénéraleDottedName } from './utils'
|
||||
import { MonthState, Options, réductionGénéraleDottedName } from './utils'
|
||||
|
||||
type Props = {
|
||||
data: MonthState[]
|
||||
onChange: (monthIndex: number, rémunérationBrute: number) => void
|
||||
onRémunérationChange: (monthIndex: number, rémunérationBrute: number) => void
|
||||
onOptionsChange: (monthIndex: number, options: Options) => void
|
||||
}
|
||||
|
||||
export default function RéductionGénéraleMoisParMois({
|
||||
data,
|
||||
onChange,
|
||||
onRémunérationChange,
|
||||
onOptionsChange,
|
||||
}: Props) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
|
@ -49,6 +51,7 @@ export default function RéductionGénéraleMoisParMois({
|
|||
{t('Rémunération brute')}
|
||||
<ExplicableRule dottedName="salarié . rémunération . brut" />
|
||||
</th>
|
||||
<th />
|
||||
<th scope="col">
|
||||
{t('Réduction générale')}
|
||||
<ExplicableRule dottedName={réductionGénéraleDottedName} light />
|
||||
|
@ -64,14 +67,27 @@ export default function RéductionGénéraleMoisParMois({
|
|||
monthName={monthName}
|
||||
data={data[monthIndex]}
|
||||
index={monthIndex}
|
||||
onChange={(monthIndex: number, rémunérationBrute: number) => {
|
||||
onChange(monthIndex, rémunérationBrute)
|
||||
onRémunérationChange={(
|
||||
monthIndex: number,
|
||||
rémunérationBrute: number
|
||||
) => {
|
||||
onRémunérationChange(monthIndex, rémunérationBrute)
|
||||
}}
|
||||
onOptionsChange={(monthIndex: number, options: Options) => {
|
||||
onOptionsChange(monthIndex, options)
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</tbody>
|
||||
</StyledTable>
|
||||
|
||||
<span id="options-description" className="sr-only">
|
||||
{t(
|
||||
'pages.simulateurs.réduction-générale.options.description',
|
||||
"Ajoute des champs pour moduler l'activité du salarié"
|
||||
)}
|
||||
</span>
|
||||
|
||||
<Warnings />
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -1,15 +1,22 @@
|
|||
import { formatValue, PublicodesExpression } from 'publicodes'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { styled } from 'styled-components'
|
||||
import { useState } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { css, styled } from 'styled-components'
|
||||
|
||||
import NumberInput from '@/components/conversation/NumberInput'
|
||||
import { Condition } from '@/components/EngineValue/Condition'
|
||||
import { Appear } from '@/components/ui/animate'
|
||||
import { useEngine } from '@/components/utils/EngineContext'
|
||||
import { SearchIcon, WarningIcon } from '@/design-system/icons'
|
||||
import { Message, NumberField } from '@/design-system'
|
||||
import { Button, HelpButtonWithPopover } from '@/design-system/buttons'
|
||||
import { FlexCenter } from '@/design-system/global-style'
|
||||
import { ChevronIcon, SearchIcon, WarningIcon } from '@/design-system/icons'
|
||||
import { Tooltip } from '@/design-system/tooltip'
|
||||
import { Body, SmallBody } from '@/design-system/typography/paragraphs'
|
||||
|
||||
import {
|
||||
MonthState,
|
||||
Options,
|
||||
réductionGénéraleDottedName,
|
||||
rémunérationBruteDottedName,
|
||||
} from '../utils'
|
||||
|
@ -20,7 +27,8 @@ type Props = {
|
|||
monthName: string
|
||||
data: MonthState
|
||||
index: number
|
||||
onChange: (monthIndex: number, rémunérationBrute: number) => void
|
||||
onRémunérationChange: (monthIndex: number, rémunérationBrute: number) => void
|
||||
onOptionsChange: (monthIndex: number, options: Options) => void
|
||||
}
|
||||
|
||||
type RémunérationBruteInput = {
|
||||
|
@ -32,19 +40,14 @@ export default function RéductionGénéraleMoisParMoisRow({
|
|||
monthName,
|
||||
data,
|
||||
index,
|
||||
onChange,
|
||||
onRémunérationChange,
|
||||
onOptionsChange,
|
||||
}: Props) {
|
||||
const { t, i18n } = useTranslation()
|
||||
const language = i18n.language
|
||||
const displayedUnit = '€'
|
||||
const engine = useEngine()
|
||||
|
||||
const onRémunérationChange = (
|
||||
monthIndex: number,
|
||||
rémunérationBrute: RémunérationBruteInput
|
||||
) => {
|
||||
onChange(monthIndex, rémunérationBrute.valeur)
|
||||
}
|
||||
const [isOptionVisible, setOptionVisible] = useState(false)
|
||||
|
||||
// TODO: enlever les 4 premières props après résolution de #3123
|
||||
const ruleInputProps = {
|
||||
|
@ -64,6 +67,21 @@ export default function RéductionGénéraleMoisParMoisRow({
|
|||
},
|
||||
}
|
||||
|
||||
const isTempsPartiel = engine.evaluate(
|
||||
'salarié . contrat . temps de travail . temps partiel'
|
||||
).nodeValue as boolean
|
||||
const additionalHours = isTempsPartiel ? 'complémentaires' : 'supplémentaires'
|
||||
const additionalHoursLabels = {
|
||||
supplémentaires: t(
|
||||
'pages.simulateurs.réduction-générale.options.label.heures-supplémentaires',
|
||||
'Heures supplémentaires'
|
||||
),
|
||||
complémentaires: t(
|
||||
'pages.simulateurs.réduction-générale.options.label.heures-complémentaires',
|
||||
'Heures complémentaires'
|
||||
),
|
||||
}
|
||||
|
||||
const tooltip = (
|
||||
<Répartition
|
||||
contexte={{
|
||||
|
@ -75,94 +93,185 @@ export default function RéductionGénéraleMoisParMoisRow({
|
|||
)
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<th scope="row">{monthName}</th>
|
||||
<td>
|
||||
<NumberInput
|
||||
{...ruleInputProps}
|
||||
id={`${rémunérationBruteDottedName.replace(
|
||||
<>
|
||||
<tr>
|
||||
<th scope="row">{monthName}</th>
|
||||
<td>
|
||||
<NumberInput
|
||||
{...ruleInputProps}
|
||||
id={`${rémunérationBruteDottedName.replace(
|
||||
/\s|\./g,
|
||||
'_'
|
||||
)}-${monthName}`}
|
||||
aria-label={`${engine.getRule(rémunérationBruteDottedName)
|
||||
?.title} (${monthName})`}
|
||||
onChange={(rémunérationBrute?: PublicodesExpression) =>
|
||||
onRémunérationChange(
|
||||
index,
|
||||
(rémunérationBrute as RémunérationBruteInput).valeur
|
||||
)
|
||||
}
|
||||
value={data.rémunérationBrute}
|
||||
formatOptions={{
|
||||
maximumFractionDigits: 2,
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<Button
|
||||
size="XXS"
|
||||
light
|
||||
onPress={() => setOptionVisible(!isOptionVisible)}
|
||||
aria-describedby="options-description"
|
||||
aria-expanded={isOptionVisible}
|
||||
aria-controls={`options-${monthName}`}
|
||||
aria-label={!isOptionVisible ? t('Déplier') : t('Replier')}
|
||||
>
|
||||
{t('Options')}
|
||||
<StyledChevron aria-hidden $isOpen={isOptionVisible} />
|
||||
</Button>
|
||||
</td>
|
||||
<td
|
||||
id={`${réductionGénéraleDottedName.replace(
|
||||
/\s|\./g,
|
||||
'_'
|
||||
)}-${monthName}`}
|
||||
aria-label={`${engine.getRule(rémunérationBruteDottedName)
|
||||
?.title} (${monthName})`}
|
||||
onChange={(rémunérationBrute?: PublicodesExpression) =>
|
||||
onRémunérationChange(
|
||||
index,
|
||||
rémunérationBrute as RémunérationBruteInput
|
||||
)
|
||||
}
|
||||
value={data.rémunérationBrute}
|
||||
formatOptions={{
|
||||
maximumFractionDigits: 2,
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
<td
|
||||
id={`${réductionGénéraleDottedName.replace(
|
||||
/\s|\./g,
|
||||
'_'
|
||||
)}-${monthName}`}
|
||||
>
|
||||
{data.réductionGénérale ? (
|
||||
<Tooltip tooltip={tooltip}>
|
||||
<StyledDiv>
|
||||
{formatValue(
|
||||
{
|
||||
nodeValue: data.réductionGénérale,
|
||||
},
|
||||
{
|
||||
displayedUnit,
|
||||
language,
|
||||
}
|
||||
)}
|
||||
<SearchIcon />
|
||||
</StyledDiv>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<StyledDiv>
|
||||
{formatValue(0, { displayedUnit, language })}
|
||||
>
|
||||
{data.réductionGénérale ? (
|
||||
<Tooltip tooltip={tooltip}>
|
||||
<FlexDiv>
|
||||
{formatValue(
|
||||
{
|
||||
nodeValue: data.réductionGénérale,
|
||||
},
|
||||
{
|
||||
displayedUnit,
|
||||
language,
|
||||
}
|
||||
)}
|
||||
<StyledSearchIcon />
|
||||
</FlexDiv>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<FlexDiv>
|
||||
{formatValue(0, { displayedUnit, language })}
|
||||
|
||||
<Condition
|
||||
expression={`${rémunérationBruteDottedName} > 1.6 * SMIC`}
|
||||
contexte={{
|
||||
[rémunérationBruteDottedName]: data.rémunérationBrute,
|
||||
}}
|
||||
>
|
||||
<Tooltip tooltip={<WarningSalaireTrans />}>
|
||||
<span className="sr-only">{t('Attention')}</span>
|
||||
<StyledWarningIcon aria-label={t('Attention')} />
|
||||
</Tooltip>
|
||||
</Condition>
|
||||
</StyledDiv>
|
||||
)}
|
||||
</td>
|
||||
<td
|
||||
id={`${réductionGénéraleDottedName.replace(
|
||||
/\s|\./g,
|
||||
'_'
|
||||
)}__régularisation-${monthName}`}
|
||||
>
|
||||
{formatValue(
|
||||
{
|
||||
nodeValue: data.régularisation,
|
||||
},
|
||||
{
|
||||
displayedUnit,
|
||||
language,
|
||||
}
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
<Condition
|
||||
expression={`${rémunérationBruteDottedName} > 1.6 * SMIC`}
|
||||
contexte={{
|
||||
[rémunérationBruteDottedName]: data.rémunérationBrute,
|
||||
}}
|
||||
>
|
||||
<Tooltip tooltip={<WarningSalaireTrans />}>
|
||||
<span className="sr-only">{t('Attention')}</span>
|
||||
<StyledWarningIcon aria-label={t('Attention')} />
|
||||
</Tooltip>
|
||||
</Condition>
|
||||
</FlexDiv>
|
||||
)}
|
||||
</td>
|
||||
<td
|
||||
id={`${réductionGénéraleDottedName.replace(
|
||||
/\s|\./g,
|
||||
'_'
|
||||
)}__régularisation-${monthName}`}
|
||||
>
|
||||
{formatValue(
|
||||
{
|
||||
nodeValue: data.régularisation,
|
||||
},
|
||||
{
|
||||
displayedUnit,
|
||||
language,
|
||||
}
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
{isOptionVisible && (
|
||||
<tr>
|
||||
<td />
|
||||
<td colSpan={4}>
|
||||
<Appear id={`options-${monthName}`}>
|
||||
<OptionContainer>
|
||||
<FlexDiv>
|
||||
<StyledSmallBody id={`heures-${additionalHours}-label`}>
|
||||
{additionalHoursLabels[additionalHours]}
|
||||
</StyledSmallBody>
|
||||
<HelpButtonWithPopover
|
||||
type="info"
|
||||
title={additionalHoursLabels[additionalHours]}
|
||||
>
|
||||
<HeuresSupplémentairesPopoverContent />
|
||||
</HelpButtonWithPopover>
|
||||
</FlexDiv>
|
||||
|
||||
<NumberField
|
||||
small={true}
|
||||
value={data.options.heuresSupplémentaires}
|
||||
onChange={(value?: number) =>
|
||||
onOptionsChange(index, {
|
||||
heuresSupplémentaires: value,
|
||||
heuresComplémentaires: 0,
|
||||
})
|
||||
}
|
||||
aria-labelledby={`heures-${additionalHours}-label`}
|
||||
displayedUnit="heures"
|
||||
/>
|
||||
</OptionContainer>
|
||||
</Appear>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: ${({ theme }) => theme.spacings.sm};
|
||||
function HeuresSupplémentairesPopoverContent() {
|
||||
return (
|
||||
<Trans i18nKey="pages.simulateurs.réduction-générale.options.popover">
|
||||
<Body>
|
||||
Le nombre d'heures supplémentaires et complémentaires est utilisé dans
|
||||
le calcul de la réduction générale : la rémunération brute est comparée
|
||||
au montant du SMIC majoré de ce nombre d'heures.
|
||||
</Body>
|
||||
<Message type="info">
|
||||
Si vous avez répondu à la question sur les heures supplémentaires ou
|
||||
complémentaires, la valeur sera écrasée par celle que vous saisissez
|
||||
mois par mois.
|
||||
</Message>
|
||||
</Trans>
|
||||
)
|
||||
}
|
||||
|
||||
const FlexDiv = styled.div`
|
||||
${FlexCenter}
|
||||
`
|
||||
|
||||
const StyledSearchIcon = styled(SearchIcon)`
|
||||
margin-left: ${({ theme }) => theme.spacings.sm};
|
||||
`
|
||||
|
||||
const StyledWarningIcon = styled(WarningIcon)`
|
||||
margin-top: ${({ theme }) => theme.spacings.xxs};
|
||||
`
|
||||
|
||||
const OptionContainer = styled.div`
|
||||
${FlexCenter}
|
||||
gap: ${({ theme }) => theme.spacings.lg};
|
||||
margin-top: -${({ theme }) => theme.spacings.md};
|
||||
`
|
||||
|
||||
const StyledSmallBody = styled(SmallBody)`
|
||||
margin: 0;
|
||||
`
|
||||
|
||||
const StyledChevron = styled(ChevronIcon)<{ $isOpen: boolean }>`
|
||||
vertical-align: middle;
|
||||
transform: rotate(-90deg);
|
||||
transition: transform 0.3s;
|
||||
${({ $isOpen }) =>
|
||||
!$isOpen &&
|
||||
css`
|
||||
transform: rotate(90deg);
|
||||
`}
|
||||
`
|
||||
|
|
Loading…
Reference in New Issue