Poursuite de la migration TypeScript
* Utilisation de la version stable de TypeScript 3.7 * Début de migration du State Redux. Plutôt que de redéfinir les types en doublon par rapport aux actions et reducers, on utilise les valeurs retournées par ces fonctions comme source pour les types globaux. * Modification de tsconfig pour meilleur typage dans VS Code * Meilleur typage de l'environnement : suppression de @types/node qui était trop large (contient tout l'environnement serveur), et remplacement par @types/webpack-env. Par ailleurs typage des variables d'environnement utilisées. * Début de migration de l'économie collaborative * Migration de nombreux composants UI * Mise à jour de dépendances pour récupérer un meilleur typage * Ajout d'un hook pour configurer les simulateurs * Suppression du higher-order component "withSitePaths", on utilise systématiquement le hook useContext. L'essentiel de l'application est maintenant migré, reste le moteur !pull/782/head
parent
49dbac252c
commit
7e2a4085a7
15
package.json
15
package.json
|
@ -44,7 +44,7 @@
|
|||
"react-i18next": "^11.0.0",
|
||||
"react-loading-skeleton": "^1.1.2",
|
||||
"react-markdown": "^4.1.0",
|
||||
"react-number-format": "^4.0.8",
|
||||
"react-number-format": "^4.3.1",
|
||||
"react-redux": "^7.0.3",
|
||||
"react-router": "^5.1.1",
|
||||
"react-router-dom": "^5.1.1",
|
||||
|
@ -53,8 +53,8 @@
|
|||
"react-transition-group": "^2.2.1",
|
||||
"react-virtualized": "^9.20.0",
|
||||
"react-virtualized-select": "^3.1.3",
|
||||
"reduce-reducers": "^0.1.2",
|
||||
"redux": "^3.7.2",
|
||||
"reduce-reducers": "^1.0.4",
|
||||
"redux": "^4.0.4",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"regenerator-runtime": "^0.13.3",
|
||||
"reselect": "^4.0.0",
|
||||
|
@ -103,17 +103,20 @@
|
|||
"@babel/preset-flow": "^7.0.0-beta.51",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@babel/preset-typescript": "^7.6.0",
|
||||
"@types/classnames": "^2.2.9",
|
||||
"@types/iframe-resizer": "^3.5.7",
|
||||
"@types/node": "^12.11.7",
|
||||
"@types/ramda": "^0.26.33",
|
||||
"@types/raven-for-redux": "^1.1.1",
|
||||
"@types/react": "^16.9.11",
|
||||
"@types/react-addons-css-transition-group": "^15.0.5",
|
||||
"@types/react-color": "^3.0.1",
|
||||
"@types/react-dom": "^16.9.3",
|
||||
"@types/react-helmet": "^5.0.13",
|
||||
"@types/react-redux": "^7.1.5",
|
||||
"@types/react-router": "^5.1.2",
|
||||
"@types/react-router-dom": "^5.1.0",
|
||||
"@types/styled-components": "^4.1.19",
|
||||
"@types/webpack-env": "^1.14.1",
|
||||
"akh": "^3.1.2",
|
||||
"autoprefixer": "^9.3.1",
|
||||
"babel-eslint": "^11.0.0-beta.0",
|
||||
|
@ -155,7 +158,7 @@
|
|||
"mock-local-storage": "^1.0.5",
|
||||
"nearley-loader": "^2.0.0",
|
||||
"postcss-loader": "^2.1.2",
|
||||
"prettier": "^1.16.4",
|
||||
"prettier": "^1.19.1",
|
||||
"ramda-fantasy": "^0.8.0",
|
||||
"raw-loader": "^0.5.1",
|
||||
"react-hot-loader": "^4.12.15",
|
||||
|
@ -167,7 +170,7 @@
|
|||
"style-loader": "^0.23.1",
|
||||
"styled-components": "^4.2.0",
|
||||
"toml-loader": "^1.0.0",
|
||||
"typescript": "^3.7.1-rc",
|
||||
"typescript": "^3.7.2",
|
||||
"url-loader": "^1.0.1",
|
||||
"webpack": "^4.39.3",
|
||||
"webpack-cli": "^3.1.2",
|
||||
|
|
|
@ -1,17 +1,25 @@
|
|||
import { ThemeColoursProvider } from 'Components/utils/withColours'
|
||||
import { SitePathProvider } from 'Components/utils/withSitePaths'
|
||||
import { SitePathProvider, SitePaths } from 'Components/utils/withSitePaths'
|
||||
import { TrackerProvider } from 'Components/utils/withTracker'
|
||||
import { createBrowserHistory } from 'history'
|
||||
import { createBrowserHistory, History } from 'history'
|
||||
import { AvailableLangs } from 'i18n'
|
||||
import i18next from 'i18next'
|
||||
import React, { PureComponent } from 'react'
|
||||
import { I18nextProvider } from 'react-i18next'
|
||||
import { Provider as ReduxProvider } from 'react-redux'
|
||||
import { Router } from 'react-router-dom'
|
||||
import reducers from 'Reducers/rootReducer'
|
||||
import { applyMiddleware, compose, createStore } from 'redux'
|
||||
import reducers, { RootState } from 'Reducers/rootReducer'
|
||||
import { applyMiddleware, compose, createStore, Middleware, Store } from 'redux'
|
||||
import thunk from 'redux-thunk'
|
||||
import Tracker from 'Tracker'
|
||||
import { inIframe } from './utils'
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__REDUX_DEVTOOLS_EXTENSION_COMPOSE__: any
|
||||
}
|
||||
}
|
||||
|
||||
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
|
||||
|
||||
if (
|
||||
|
@ -33,8 +41,20 @@ if (
|
|||
})
|
||||
}
|
||||
|
||||
export default class Provider extends PureComponent {
|
||||
constructor(props) {
|
||||
type ProviderProps = {
|
||||
tracker: Tracker
|
||||
basename: string
|
||||
sitePaths: SitePaths
|
||||
language: AvailableLangs
|
||||
initialStore: RootState
|
||||
onStoreCreated: (store: Store) => void
|
||||
reduxMiddlewares: Array<Middleware>
|
||||
}
|
||||
|
||||
export default class Provider extends PureComponent<ProviderProps> {
|
||||
history: History
|
||||
store: Store
|
||||
constructor(props: ProviderProps) {
|
||||
super(props)
|
||||
this.history = createBrowserHistory({
|
||||
basename: process.env.NODE_ENV === 'production' ? '' : this.props.basename
|
||||
|
@ -56,7 +76,7 @@ export default class Provider extends PureComponent {
|
|||
this.props.initialStore.lang = this.props.language
|
||||
}
|
||||
this.store = createStore(reducers, this.props.initialStore, storeEnhancer)
|
||||
this.props.onStoreCreated && this.props.onStoreCreated(this.store)
|
||||
this.props.onStoreCreated?.(this.store)
|
||||
|
||||
// Remove loader
|
||||
var css = document.createElement('style')
|
||||
|
@ -82,7 +102,8 @@ export default class Provider extends PureComponent {
|
|||
// If IE < 11 display nothing
|
||||
<ReduxProvider store={this.store}>
|
||||
<ThemeColoursProvider
|
||||
colour={iframeCouleur && decodeURIComponent(iframeCouleur)}>
|
||||
colour={iframeCouleur && decodeURIComponent(iframeCouleur)}
|
||||
>
|
||||
<TrackerProvider value={this.props.tracker}>
|
||||
<SitePathProvider value={this.props.sitePaths}>
|
||||
<I18nextProvider i18n={i18next}>
|
|
@ -1,17 +1,14 @@
|
|||
import { History, Location } from 'history'
|
||||
import { debounce } from './utils'
|
||||
|
||||
declare global {
|
||||
interface Window { _paq: any; }
|
||||
interface Window {
|
||||
_paq: any
|
||||
}
|
||||
}
|
||||
|
||||
function push(args: ['trackPageView']): void
|
||||
function push(args: ['trackEvent']): void
|
||||
function push(args: ['trackEvent', string, string]): void
|
||||
function push(args: ['trackEvent', string, string, string]): void
|
||||
function push(args: ['trackEvent', string, string, string, string, string]): void
|
||||
function push(args: ['trackEvent', string, string, string, number]): void
|
||||
function push() {}
|
||||
type PushType = typeof push
|
||||
type PushArgs = ['trackPageView'] | ['trackEvent', ...Array<string | number>]
|
||||
type PushType = (args: PushArgs) => void
|
||||
|
||||
export default class Tracker {
|
||||
push: PushType
|
||||
|
@ -23,7 +20,7 @@ export default class Tracker {
|
|||
this.push = debounce(200, pushFunction) as PushType
|
||||
}
|
||||
|
||||
connectToHistory(history) {
|
||||
connectToHistory(history: History) {
|
||||
this.unlistenFromHistory = history.listen(loc => {
|
||||
this.track(loc)
|
||||
})
|
||||
|
@ -52,5 +49,5 @@ export default class Tracker {
|
|||
}
|
||||
|
||||
export const devTracker = new Tracker(
|
||||
(console?.log?.bind(console)) ?? (() => {}) // eslint-disable-line no-console
|
||||
console?.log?.bind(console) ?? (() => {}) // eslint-disable-line no-console
|
||||
)
|
||||
|
|
|
@ -1,98 +0,0 @@
|
|||
import type {
|
||||
ResetSimulationAction,
|
||||
LoadPreviousSimulationAction,
|
||||
StepAction,
|
||||
DeletePreviousSimulationAction,
|
||||
SetSimulationConfigAction,
|
||||
SetSituationBranchAction
|
||||
} from 'Types/ActionsTypes'
|
||||
import { deletePersistedSimulation } from '../storage/persistSimulation'
|
||||
import type { Thunk } from 'Types/ActionsTypes'
|
||||
|
||||
export const resetSimulation = () => (dispatch: any => void): void => {
|
||||
dispatch(
|
||||
({
|
||||
type: 'RESET_SIMULATION'
|
||||
}: ResetSimulationAction)
|
||||
)
|
||||
}
|
||||
|
||||
export const goToQuestion = (question: string): StepAction => ({
|
||||
type: 'STEP_ACTION',
|
||||
name: 'unfold',
|
||||
step: question
|
||||
})
|
||||
|
||||
export const validateStepWithValue = (
|
||||
dottedName,
|
||||
value: any
|
||||
): Thunk<StepAction> => dispatch => {
|
||||
dispatch(updateSituation(dottedName, value))
|
||||
dispatch({
|
||||
type: 'STEP_ACTION',
|
||||
name: 'fold',
|
||||
step: dottedName
|
||||
})
|
||||
}
|
||||
|
||||
export const setSituationBranch = (id: number): SetSituationBranchAction => ({
|
||||
type: 'SET_SITUATION_BRANCH',
|
||||
id
|
||||
})
|
||||
|
||||
export const setSimulationConfig = (
|
||||
config: Object
|
||||
): Thunk<SetSimulationConfigAction> => (dispatch, _, { history }): void => {
|
||||
const url = history.location.pathname
|
||||
dispatch({
|
||||
type: 'SET_SIMULATION',
|
||||
url,
|
||||
config
|
||||
})
|
||||
}
|
||||
|
||||
export const deletePreviousSimulation = () => (
|
||||
dispatch: DeletePreviousSimulationAction => void
|
||||
) => {
|
||||
dispatch({
|
||||
type: 'DELETE_PREVIOUS_SIMULATION'
|
||||
})
|
||||
deletePersistedSimulation()
|
||||
}
|
||||
|
||||
export const updateSituation = (fieldName, value) => ({
|
||||
type: 'UPDATE_SITUATION',
|
||||
fieldName,
|
||||
value
|
||||
})
|
||||
|
||||
export const updatePeriod = toPeriod => ({
|
||||
type: 'UPDATE_PERIOD',
|
||||
toPeriod
|
||||
})
|
||||
|
||||
// $FlowFixMe
|
||||
export function setExample(name, situation, dottedName) {
|
||||
return { type: 'SET_EXAMPLE', name, situation, dottedName }
|
||||
}
|
||||
|
||||
export const goBackToSimulation = (): Thunk<any> => (
|
||||
dispatch,
|
||||
getState,
|
||||
{ history }
|
||||
) => {
|
||||
dispatch({ type: 'SET_EXEMPLE', name: null })
|
||||
history.push(getState().simulation.url)
|
||||
}
|
||||
|
||||
export function loadPreviousSimulation(): LoadPreviousSimulationAction {
|
||||
return {
|
||||
type: 'LOAD_PREVIOUS_SIMULATION'
|
||||
}
|
||||
}
|
||||
|
||||
export function hideControl(id: string) {
|
||||
return { type: 'HIDE_CONTROL', id }
|
||||
}
|
||||
|
||||
export const EXPLAIN_VARIABLE = 'EXPLAIN_VARIABLE'
|
|
@ -0,0 +1,157 @@
|
|||
import { SitePaths } from 'Components/utils/withSitePaths'
|
||||
import { History } from 'history'
|
||||
import { RootState } from 'Reducers/rootReducer'
|
||||
import { ThunkAction } from 'redux-thunk'
|
||||
import { DottedName } from 'Types/rule'
|
||||
import { deletePersistedSimulation } from '../storage/persistSimulation'
|
||||
|
||||
export type Action =
|
||||
| ResetSimulationAction
|
||||
| StepAction
|
||||
| UpdateAction
|
||||
| SetSimulationConfigAction
|
||||
| DeletePreviousSimulationAction
|
||||
| SetExempleAction
|
||||
| ExplainVariableAction
|
||||
| UpdatePeriodAction
|
||||
| HideControlAction
|
||||
| LoadPreviousSimulationAction
|
||||
| SetSituationBranchAction
|
||||
| SetActiveTargetAction
|
||||
|
||||
type ThunkResult<R> = ThunkAction<
|
||||
R,
|
||||
RootState,
|
||||
{ history: History; sitePaths: SitePaths },
|
||||
Action
|
||||
>
|
||||
|
||||
type StepAction = {
|
||||
type: 'STEP_ACTION'
|
||||
name: 'fold' | 'unfold'
|
||||
step: string
|
||||
}
|
||||
|
||||
type SetSimulationConfigAction = {
|
||||
type: 'SET_SIMULATION'
|
||||
url: string
|
||||
config: Object
|
||||
}
|
||||
|
||||
type DeletePreviousSimulationAction = {
|
||||
type: 'DELETE_PREVIOUS_SIMULATION'
|
||||
}
|
||||
|
||||
type SetExempleAction = {
|
||||
type: 'SET_EXAMPLE'
|
||||
name: null | string
|
||||
situation?: object
|
||||
dottedName?: string
|
||||
}
|
||||
|
||||
type ResetSimulationAction = ReturnType<typeof resetSimulation>
|
||||
type UpdateAction = ReturnType<typeof updateSituation>
|
||||
type UpdatePeriodAction = ReturnType<typeof updatePeriod>
|
||||
type LoadPreviousSimulationAction = ReturnType<typeof loadPreviousSimulation>
|
||||
type SetSituationBranchAction = ReturnType<typeof setSituationBranch>
|
||||
type SetActiveTargetAction = ReturnType<typeof setActiveTarget>
|
||||
type HideControlAction = ReturnType<typeof hideControl>
|
||||
type ExplainVariableAction = ReturnType<typeof explainVariable>
|
||||
|
||||
export const resetSimulation = () =>
|
||||
({
|
||||
type: 'RESET_SIMULATION'
|
||||
} as const)
|
||||
|
||||
export const goToQuestion = (question: string) =>
|
||||
({
|
||||
type: 'STEP_ACTION',
|
||||
name: 'unfold',
|
||||
step: question
|
||||
} as const)
|
||||
|
||||
export const validateStepWithValue = (
|
||||
dottedName: DottedName,
|
||||
value: any
|
||||
): ThunkResult<void> => dispatch => {
|
||||
dispatch(updateSituation(dottedName, value))
|
||||
dispatch({
|
||||
type: 'STEP_ACTION',
|
||||
name: 'fold',
|
||||
step: dottedName
|
||||
})
|
||||
}
|
||||
|
||||
export const setSituationBranch = (id: number) =>
|
||||
({
|
||||
type: 'SET_SITUATION_BRANCH',
|
||||
id
|
||||
} as const)
|
||||
|
||||
export const setSimulationConfig = (config: Object): ThunkResult<void> => (
|
||||
dispatch,
|
||||
_,
|
||||
{ history }
|
||||
): void => {
|
||||
const url = history.location.pathname
|
||||
dispatch({
|
||||
type: 'SET_SIMULATION',
|
||||
url,
|
||||
config
|
||||
})
|
||||
}
|
||||
|
||||
export const setActiveTarget = (targetName: string) =>
|
||||
({
|
||||
type: 'SET_ACTIVE_TARGET_INPUT',
|
||||
name: targetName
|
||||
} as const)
|
||||
|
||||
export const deletePreviousSimulation = (): ThunkResult<void> => dispatch => {
|
||||
dispatch({
|
||||
type: 'DELETE_PREVIOUS_SIMULATION'
|
||||
})
|
||||
deletePersistedSimulation()
|
||||
}
|
||||
|
||||
export const updateSituation = (fieldName: DottedName, value: any) =>
|
||||
({
|
||||
type: 'UPDATE_SITUATION',
|
||||
fieldName,
|
||||
value
|
||||
} as const)
|
||||
|
||||
export const updatePeriod = (toPeriod: string) =>
|
||||
({
|
||||
type: 'UPDATE_PERIOD',
|
||||
toPeriod
|
||||
} as const)
|
||||
|
||||
export function setExample(name: string, situation, dottedName: string) {
|
||||
return { type: 'SET_EXAMPLE', name, situation, dottedName } as const
|
||||
}
|
||||
|
||||
export const goBackToSimulation = (): ThunkResult<void> => (
|
||||
dispatch,
|
||||
getState,
|
||||
{ history }
|
||||
) => {
|
||||
dispatch({ type: 'SET_EXAMPLE', name: null })
|
||||
history.push(getState().simulation.url)
|
||||
}
|
||||
|
||||
export function loadPreviousSimulation() {
|
||||
return {
|
||||
type: 'LOAD_PREVIOUS_SIMULATION'
|
||||
} as const
|
||||
}
|
||||
|
||||
export function hideControl(id: string) {
|
||||
return { type: 'HIDE_CONTROL', id } as const
|
||||
}
|
||||
|
||||
export const explainVariable = (variableName = null) =>
|
||||
({
|
||||
type: 'EXPLAIN_VARIABLE',
|
||||
variableName
|
||||
} as const)
|
|
@ -1,21 +0,0 @@
|
|||
import type {
|
||||
InitializeCompanyCreationChecklistAction,
|
||||
CheckCompanyCreationItemAction
|
||||
} from 'Types/companyCreationChecklistTypes'
|
||||
|
||||
export const initializeCompanyCreationChecklist = (
|
||||
statusName: string,
|
||||
checklistItems: Array<string>
|
||||
) =>
|
||||
({
|
||||
type: 'INITIALIZE_COMPANY_CREATION_CHECKLIST',
|
||||
checklistItems,
|
||||
statusName
|
||||
}: InitializeCompanyCreationChecklistAction)
|
||||
|
||||
export const checkCompanyCreationItem = (name: string, checked: boolean) =>
|
||||
({
|
||||
type: 'CHECK_COMPANY_CREATION_ITEM',
|
||||
name,
|
||||
checked
|
||||
}: CheckCompanyCreationItemAction)
|
|
@ -0,0 +1,29 @@
|
|||
import { LegalStatus } from 'Selectors/companyStatusSelectors'
|
||||
|
||||
export type Action =
|
||||
| InitializeCompanyCreationChecklistAction
|
||||
| CheckCompanyCreationItemAction
|
||||
|
||||
type InitializeCompanyCreationChecklistAction = ReturnType<
|
||||
typeof initializeCompanyCreationChecklist
|
||||
>
|
||||
type CheckCompanyCreationItemAction = ReturnType<
|
||||
typeof checkCompanyCreationItem
|
||||
>
|
||||
|
||||
export const initializeCompanyCreationChecklist = (
|
||||
statusName: LegalStatus,
|
||||
checklistItems: Array<string>
|
||||
) =>
|
||||
({
|
||||
type: 'INITIALIZE_COMPANY_CREATION_CHECKLIST',
|
||||
checklistItems,
|
||||
statusName
|
||||
} as const)
|
||||
|
||||
export const checkCompanyCreationItem = (name: string, checked: boolean) =>
|
||||
({
|
||||
type: 'CHECK_COMPANY_CREATION_ITEM',
|
||||
name,
|
||||
checked
|
||||
} as const)
|
|
@ -1,6 +1,6 @@
|
|||
import { fetchCompanyDetails } from '../api/sirene'
|
||||
|
||||
const fetchCommuneDetails = function (codeCommune) {
|
||||
const fetchCommuneDetails = function(codeCommune) {
|
||||
return fetch(
|
||||
`https://geo.api.gouv.fr/communes/${codeCommune}?fields=departement,region`
|
||||
).then(response => {
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import type {
|
||||
InitializeHiringChecklistAction,
|
||||
CheckHiringItemAction
|
||||
} from 'Types/hiringChecklistTypes'
|
||||
export type Action = InitializeHiringChecklistAction | CheckHiringItemAction
|
||||
|
||||
type InitializeHiringChecklistAction = ReturnType<typeof initializeHiringChecklist>
|
||||
type CheckHiringItemAction = ReturnType<typeof checkHiringItem>
|
||||
|
||||
export const initializeHiringChecklist = (checklistItems: Array<string>) =>
|
||||
({
|
||||
type: 'INITIALIZE_HIRING_CHECKLIST',
|
||||
checklistItems
|
||||
}: InitializeHiringChecklistAction)
|
||||
} as const)
|
||||
|
||||
export const checkHiringItem = (name: string, checked: boolean) =>
|
||||
({
|
||||
type: 'CHECK_HIRING_ITEM',
|
||||
name,
|
||||
checked
|
||||
}: CheckHiringItemAction)
|
||||
} as const)
|
|
@ -1,7 +1,7 @@
|
|||
const isSIREN = input => input.match(/^[\s]*([\d][\s]*){9}$/)
|
||||
const isSIRET = input => input.match(/^[\s]*([\d][\s]*){14}$/)
|
||||
const isSIREN = (input: string) => input.match(/^[\s]*([\d][\s]*){9}$/)
|
||||
const isSIRET = (input: string) => input.match(/^[\s]*([\d][\s]*){14}$/)
|
||||
|
||||
export async function fetchCompanyDetails(siren) {
|
||||
export async function fetchCompanyDetails(siren: string) {
|
||||
const response = await fetch(
|
||||
`https://entreprise.data.gouv.fr/api/sirene/v3/unites_legales/${siren.replace(
|
||||
/[\s]/g,
|
||||
|
@ -15,7 +15,7 @@ export async function fetchCompanyDetails(siren) {
|
|||
return json.unite_legale
|
||||
}
|
||||
|
||||
export async function searchDenominationOrSiren(value) {
|
||||
export async function searchDenominationOrSiren(value: string) {
|
||||
if (isSIRET(value)) {
|
||||
value = value.replace(/[\s]/g, '').slice(0, 9)
|
||||
}
|
||||
|
@ -25,7 +25,12 @@ export async function searchDenominationOrSiren(value) {
|
|||
return searchFullText(value)
|
||||
}
|
||||
|
||||
async function searchFullText(text) {
|
||||
export type Etablissement = {
|
||||
siren: string
|
||||
denomination?: string
|
||||
}
|
||||
|
||||
async function searchFullText(text: string): Promise<Array<Etablissement>> {
|
||||
const response = await fetch(
|
||||
`https://entreprise.data.gouv.fr/api/sirene/v1/full_text/${text}?per_page=5`
|
||||
)
|
|
@ -1,30 +0,0 @@
|
|||
import React from 'react'
|
||||
import emoji from 'react-easy-emoji'
|
||||
import { connect } from 'react-redux'
|
||||
import { firstStepCompletedSelector } from 'Selectors/analyseSelectors'
|
||||
import Animate from 'Ui/animate'
|
||||
import './Banner.css'
|
||||
import type { Node } from 'react'
|
||||
import type { State } from 'Types/State'
|
||||
type PropTypes = {
|
||||
hidden: boolean,
|
||||
children: Node,
|
||||
icon?: string
|
||||
}
|
||||
|
||||
let Banner = ({ hidden = false, children, icon }: PropTypes) =>
|
||||
!hidden ? (
|
||||
<Animate.fadeIn>
|
||||
<div className="ui__ banner">
|
||||
{icon && emoji(icon)}
|
||||
<p>{children}</p>
|
||||
</div>
|
||||
</Animate.fadeIn>
|
||||
) : null
|
||||
|
||||
export default connect(
|
||||
(state: State, { hidden }: PropTypes) => ({
|
||||
hidden: hidden || firstStepCompletedSelector(state)
|
||||
}),
|
||||
{}
|
||||
)(Banner)
|
|
@ -0,0 +1,29 @@
|
|||
import React from 'react'
|
||||
import emoji from 'react-easy-emoji'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { firstStepCompletedSelector } from 'Selectors/analyseSelectors'
|
||||
import Animate from 'Ui/animate'
|
||||
import './Banner.css'
|
||||
|
||||
type BannerProps = {
|
||||
children: React.ReactNode
|
||||
hidden?: boolean
|
||||
icon?: string
|
||||
}
|
||||
|
||||
export default function Banner({
|
||||
children,
|
||||
hidden: hiddenProp = false,
|
||||
icon
|
||||
}: BannerProps) {
|
||||
const hiddenState = useSelector(firstStepCompletedSelector)
|
||||
const hidden = hiddenProp || hiddenState
|
||||
return !hidden ? (
|
||||
<Animate.fadeIn>
|
||||
<div className="ui__ banner">
|
||||
{icon && emoji(icon)}
|
||||
<p>{children}</p>
|
||||
</div>
|
||||
</Animate.fadeIn>
|
||||
) : null
|
||||
}
|
|
@ -2,9 +2,9 @@ import { T } from 'Components'
|
|||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Skeleton from 'react-loading-skeleton'
|
||||
import { fetchCompanyDetails } from '../api/sirene'
|
||||
import { Etablissement, fetchCompanyDetails } from '../api/sirene'
|
||||
|
||||
export default function CompanyDetails({ siren, denomination }) {
|
||||
export default function CompanyDetails({ siren, denomination }: Etablissement) {
|
||||
const { i18n } = useTranslation()
|
||||
const DateFormatter = useMemo(
|
||||
() =>
|
|
@ -1,10 +1,18 @@
|
|||
import classnames from 'classnames'
|
||||
import React, { useRef, useState } from 'react'
|
||||
import { currencyFormat } from 'Engine/format'
|
||||
import NumberFormat from 'react-number-format'
|
||||
import React, { useRef, useState } from 'react'
|
||||
import NumberFormat, { NumberFormatProps } from 'react-number-format'
|
||||
import { debounce } from '../../utils'
|
||||
import './CurrencyInput.css'
|
||||
|
||||
type CurrencyInputProps = NumberFormatProps & {
|
||||
value?: string | number
|
||||
debounce?: number
|
||||
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
|
||||
currencySymbol?: string
|
||||
language?: Parameters<typeof currencyFormat>[0]
|
||||
}
|
||||
|
||||
export default function CurrencyInput({
|
||||
value: valueProp = '',
|
||||
debounce: debounceTimeout,
|
||||
|
@ -13,7 +21,7 @@ export default function CurrencyInput({
|
|||
language,
|
||||
className,
|
||||
...forwardedProps
|
||||
}) {
|
||||
}: CurrencyInputProps) {
|
||||
const [initialValue, setInitialValue] = useState(valueProp)
|
||||
const [currentValue, setCurrentValue] = useState(valueProp)
|
||||
const onChangeDebounced = useRef(
|
||||
|
@ -23,7 +31,7 @@ export default function CurrencyInput({
|
|||
// the DOM `event` in its custom `onValueChange` handler
|
||||
const nextValue = useRef(null)
|
||||
|
||||
const inputRef = useRef()
|
||||
const inputRef = useRef<HTMLInputElement>()
|
||||
|
||||
// When the component is rendered with a new "value" prop, we reset our local state
|
||||
if (valueProp !== initialValue) {
|
||||
|
@ -31,7 +39,7 @@ export default function CurrencyInput({
|
|||
setInitialValue(valueProp)
|
||||
}
|
||||
|
||||
const handleChange = event => {
|
||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
// Only trigger the `onChange` event if the value has changed -- and not
|
||||
// only its formating, we don't want to call it when a dot is added in `12.`
|
||||
// for instance
|
||||
|
@ -63,7 +71,8 @@ export default function CurrencyInput({
|
|||
<div
|
||||
className={classnames(className, 'currencyInput__container')}
|
||||
{...(valueLength > 5 ? { style: { width } } : {})}
|
||||
onClick={() => inputRef.current.focus()}>
|
||||
onClick={() => inputRef.current.focus()}
|
||||
>
|
||||
{!currentValue && isCurrencyPrefixed && currencySymbol}
|
||||
<NumberFormat
|
||||
{...forwardedProps}
|
|
@ -1,21 +1,19 @@
|
|||
|
||||
import { ScrollToElement } from 'Components/utils/Scroll'
|
||||
import withTracker from 'Components/utils/withTracker'
|
||||
import React, { useRef } from 'react'
|
||||
import { TrackerContext } from 'Components/utils/withTracker'
|
||||
import React, { useContext, useRef } from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import type { Tracker } from 'Components/utils/withTracker'
|
||||
|
||||
type Props = { onEnd: () => void, tracker: Tracker, onCancel: () => void }
|
||||
type Props = { onEnd: () => void; onCancel: () => void }
|
||||
|
||||
function FeedbackForm({ onEnd, onCancel, tracker }: Props) {
|
||||
const formRef = useRef()
|
||||
export default function FeedbackForm({ onEnd, onCancel }: Props) {
|
||||
const formRef = useRef<HTMLFormElement>()
|
||||
const tracker = useContext(TrackerContext)
|
||||
|
||||
const handleFormSubmit = e => {
|
||||
const handleFormSubmit = (e: React.FormEvent): void => {
|
||||
tracker.push(['trackEvent', 'Feedback', 'written feedback submitted'])
|
||||
e.preventDefault()
|
||||
fetch('/', {
|
||||
method: 'POST',
|
||||
// $FlowFixMe
|
||||
body: new FormData(formRef.current)
|
||||
})
|
||||
onEnd()
|
||||
|
@ -28,7 +26,8 @@ function FeedbackForm({ onEnd, onCancel, tracker }: Props) {
|
|||
onClick={() => onCancel()}
|
||||
className="ui__ link-button"
|
||||
style={{ textDecoration: 'none', marginLeft: '0.3rem' }}
|
||||
aria-label="close">
|
||||
aria-label="close"
|
||||
>
|
||||
X
|
||||
</button>
|
||||
</div>
|
||||
|
@ -37,7 +36,8 @@ function FeedbackForm({ onEnd, onCancel, tracker }: Props) {
|
|||
style={{ flex: 1 }}
|
||||
method="post"
|
||||
ref={formRef}
|
||||
onSubmit={handleFormSubmit}>
|
||||
onSubmit={handleFormSubmit}
|
||||
>
|
||||
<input type="hidden" name="form-name" value="feedback" />
|
||||
<label htmlFor="message">
|
||||
<p>
|
||||
|
@ -50,7 +50,7 @@ function FeedbackForm({ onEnd, onCancel, tracker }: Props) {
|
|||
</label>
|
||||
<textarea
|
||||
name="message"
|
||||
rows="5"
|
||||
rows={5}
|
||||
style={{ resize: 'none', width: '100%', padding: '0.6rem' }}
|
||||
/>
|
||||
<br />
|
||||
|
@ -75,5 +75,3 @@ function FeedbackForm({ onEnd, onCancel, tracker }: Props) {
|
|||
</ScrollToElement>
|
||||
)
|
||||
}
|
||||
|
||||
export default withTracker(FeedbackForm)
|
|
@ -1,170 +0,0 @@
|
|||
import withTracker from 'Components/utils/withTracker'
|
||||
import React, { Component } from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { withRouter } from 'react-router'
|
||||
import { compose } from 'redux'
|
||||
import safeLocalStorage from '../../storage/safeLocalStorage'
|
||||
import './Feedback.css'
|
||||
import Form from './FeedbackForm'
|
||||
import type { Tracker } from 'Components/utils/withTracker'
|
||||
import type { Location } from 'react-router-dom'
|
||||
import type { Node } from 'react'
|
||||
|
||||
type OwnProps = {
|
||||
blacklist: Array<string>,
|
||||
customMessage?: Node,
|
||||
customEventName?: string
|
||||
}
|
||||
type Props = OwnProps & {
|
||||
location: Location,
|
||||
tracker: Tracker
|
||||
}
|
||||
type State = {
|
||||
showForm: boolean,
|
||||
feedbackAlreadyGiven: boolean,
|
||||
showThanks: boolean
|
||||
}
|
||||
|
||||
const localStorageKey = (feedback: [string, string]) =>
|
||||
`app::feedback::${feedback.join('::')}`
|
||||
const saveFeedbackOccurrenceInLocalStorage = ([name, path, rating]: [
|
||||
string,
|
||||
string,
|
||||
number
|
||||
]) => {
|
||||
safeLocalStorage.setItem(
|
||||
localStorageKey([name, path]),
|
||||
JSON.stringify(rating)
|
||||
)
|
||||
}
|
||||
const feedbackAlreadyGiven = (feedback: [string, string]) => {
|
||||
return !!safeLocalStorage.getItem(localStorageKey(feedback))
|
||||
}
|
||||
|
||||
class PageFeedback extends Component<Props, State> {
|
||||
static defaultProps = {
|
||||
blacklist: []
|
||||
}
|
||||
feedbackAlreadyGiven: boolean
|
||||
feedbackAlreadyGiven = false
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
showForm: false,
|
||||
showThanks: false,
|
||||
feedbackAlreadyGiven: feedbackAlreadyGiven([
|
||||
this.props.customEventName || 'rate page usefulness',
|
||||
this.props.location.pathname
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
handleFeedback = ({ useful }) => {
|
||||
this.props.tracker.push([
|
||||
'trackEvent',
|
||||
'Feedback',
|
||||
useful ? 'positive rating' : 'negative rating',
|
||||
this.props.location.pathname
|
||||
])
|
||||
const feedback = [
|
||||
this.props.customEventName || 'rate page usefulness',
|
||||
this.props.location.pathname,
|
||||
useful ? 10 : 0.1
|
||||
]
|
||||
this.props.tracker.push(['trackEvent', 'Feedback', ...feedback])
|
||||
saveFeedbackOccurrenceInLocalStorage(feedback)
|
||||
this.setState({
|
||||
showThanks: useful,
|
||||
feedbackAlreadyGiven: true,
|
||||
showForm: !useful
|
||||
})
|
||||
}
|
||||
handleErrorReporting = () => {
|
||||
this.props.tracker.push([
|
||||
'trackEvent',
|
||||
'Feedback',
|
||||
'report error',
|
||||
this.props.location.pathname
|
||||
])
|
||||
this.setState({ showForm: true })
|
||||
}
|
||||
render() {
|
||||
if (
|
||||
this.state.feedbackAlreadyGiven &&
|
||||
!this.state.showForm &&
|
||||
!this.state.showThanks
|
||||
) {
|
||||
return null
|
||||
}
|
||||
const pathname =
|
||||
this.props.location.pathname === '/' ? '' : this.props.location.pathname
|
||||
return (
|
||||
!this.props.blacklist.includes(pathname) && (
|
||||
<div
|
||||
className="ui__ container"
|
||||
style={{ display: 'flex', justifyContent: 'center' }}>
|
||||
<div className="feedback-page ui__ notice ">
|
||||
{!this.state.showForm && !this.state.showThanks && (
|
||||
<>
|
||||
<div style={{ flexShrink: 0 }}>
|
||||
{this.props.customMessage || (
|
||||
<Trans i18nKey="feedback.question">
|
||||
Cette page vous est utile ?
|
||||
</Trans>
|
||||
)}{' '}
|
||||
</div>
|
||||
<div className="feedbackButtons">
|
||||
<button
|
||||
className="ui__ link-button"
|
||||
onClick={() => this.handleFeedback({ useful: true })}>
|
||||
<Trans>Oui</Trans>
|
||||
</button>{' '}
|
||||
<button
|
||||
className="ui__ link-button"
|
||||
onClick={() => this.handleFeedback({ useful: false })}>
|
||||
<Trans>Non</Trans>
|
||||
</button>
|
||||
<button
|
||||
className="ui__ link-button"
|
||||
onClick={this.handleErrorReporting}>
|
||||
<Trans i18nKey="feedback.reportError">
|
||||
Faire une suggestion
|
||||
</Trans>
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{this.state.showThanks && (
|
||||
<div>
|
||||
<Trans i18nKey="feedback.thanks">
|
||||
Merci pour votre retour ! Vous pouvez nous contacter
|
||||
directement à{' '}
|
||||
<a href="mailto:contact@mon-entreprise.beta.gouv.fr">
|
||||
contact@mon-entreprise.beta.gouv.fr
|
||||
</a>
|
||||
</Trans>
|
||||
</div>
|
||||
)}
|
||||
{this.state.showForm && (
|
||||
<Form
|
||||
onEnd={() =>
|
||||
this.setState({ showThanks: true, showForm: false })
|
||||
}
|
||||
onCancel={() =>
|
||||
this.setState({ showThanks: false, showForm: false })
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
const PageFeedbackWithRouter = ({ location, ...props }) => (
|
||||
<PageFeedback {...props} location={location} key={location.pathname} />
|
||||
)
|
||||
export default compose(
|
||||
withRouter,
|
||||
withTracker
|
||||
)(PageFeedbackWithRouter)
|
|
@ -0,0 +1,144 @@
|
|||
import { TrackerContext } from 'Components/utils/withTracker'
|
||||
import React, { useCallback, useContext, useState } from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { useLocation } from 'react-router'
|
||||
import safeLocalStorage from '../../storage/safeLocalStorage'
|
||||
import './Feedback.css'
|
||||
import Form from './FeedbackForm'
|
||||
|
||||
type PageFeedbackProps = {
|
||||
blacklist?: Array<string>
|
||||
customMessage?: React.ReactNode
|
||||
customEventName?: string
|
||||
}
|
||||
|
||||
const localStorageKey = (feedback: [string, string]) =>
|
||||
`app::feedback::${feedback.join('::')}`
|
||||
const saveFeedbackOccurrenceInLocalStorage = ([name, path, rating]: [
|
||||
string,
|
||||
string,
|
||||
number
|
||||
]) => {
|
||||
safeLocalStorage.setItem(
|
||||
localStorageKey([name, path]),
|
||||
JSON.stringify(rating)
|
||||
)
|
||||
}
|
||||
const feedbackAlreadyGiven = (feedback: [string, string]) => {
|
||||
return !!safeLocalStorage.getItem(localStorageKey(feedback))
|
||||
}
|
||||
|
||||
export default function PageFeedback({
|
||||
blacklist = [],
|
||||
customMessage,
|
||||
customEventName
|
||||
}: PageFeedbackProps) {
|
||||
const location = useLocation()
|
||||
const tracker = useContext(TrackerContext)
|
||||
const [state, setState] = useState({
|
||||
showForm: false,
|
||||
showThanks: false,
|
||||
feedbackAlreadyGiven: feedbackAlreadyGiven([
|
||||
customEventName || 'rate page usefulness',
|
||||
location.pathname
|
||||
])
|
||||
})
|
||||
|
||||
const handleFeedback = useCallback(({ useful }: { useful: boolean }) => {
|
||||
tracker.push([
|
||||
'trackEvent',
|
||||
'Feedback',
|
||||
useful ? 'positive rating' : 'negative rating',
|
||||
location.pathname
|
||||
])
|
||||
const feedback = [
|
||||
customEventName || 'rate page usefulness',
|
||||
location.pathname,
|
||||
useful ? 10 : 0.1
|
||||
] as [string, string, number]
|
||||
tracker.push(['trackEvent', 'Feedback', ...feedback])
|
||||
saveFeedbackOccurrenceInLocalStorage(feedback)
|
||||
setState({
|
||||
showThanks: useful,
|
||||
feedbackAlreadyGiven: true,
|
||||
showForm: !useful
|
||||
})
|
||||
}, [])
|
||||
|
||||
const handleErrorReporting = useCallback(() => {
|
||||
tracker.push(['trackEvent', 'Feedback', 'report error', location.pathname])
|
||||
setState({ ...state, showForm: true })
|
||||
}, [])
|
||||
|
||||
if (state.feedbackAlreadyGiven && !state.showForm && !state.showThanks) {
|
||||
return null
|
||||
}
|
||||
const pathname = location.pathname === '/' ? '' : location.pathname
|
||||
|
||||
if (blacklist.includes(pathname)) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="ui__ container"
|
||||
style={{ display: 'flex', justifyContent: 'center' }}
|
||||
>
|
||||
<div className="feedback-page ui__ notice ">
|
||||
{!state.showForm && !state.showThanks && (
|
||||
<>
|
||||
<div style={{ flexShrink: 0 }}>
|
||||
{customMessage || (
|
||||
<Trans i18nKey="feedback.question">
|
||||
Cette page vous est utile ?
|
||||
</Trans>
|
||||
)}{' '}
|
||||
</div>
|
||||
<div className="feedbackButtons">
|
||||
<button
|
||||
className="ui__ link-button"
|
||||
onClick={() => handleFeedback({ useful: true })}
|
||||
>
|
||||
<Trans>Oui</Trans>
|
||||
</button>{' '}
|
||||
<button
|
||||
className="ui__ link-button"
|
||||
onClick={() => handleFeedback({ useful: false })}
|
||||
>
|
||||
<Trans>Non</Trans>
|
||||
</button>
|
||||
<button
|
||||
className="ui__ link-button"
|
||||
onClick={handleErrorReporting}
|
||||
>
|
||||
<Trans i18nKey="feedback.reportError">
|
||||
Faire une suggestion
|
||||
</Trans>
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{state.showThanks && (
|
||||
<div>
|
||||
<Trans i18nKey="feedback.thanks">
|
||||
Merci pour votre retour ! Vous pouvez nous contacter directement à{' '}
|
||||
<a href="mailto:contact@mon-entreprise.beta.gouv.fr">
|
||||
contact@mon-entreprise.beta.gouv.fr
|
||||
</a>
|
||||
</Trans>
|
||||
</div>
|
||||
)}
|
||||
{state.showForm && (
|
||||
<Form
|
||||
onEnd={() =>
|
||||
setState({ ...state, showThanks: true, showForm: false })
|
||||
}
|
||||
onCancel={() =>
|
||||
setState({ ...state, showThanks: false, showForm: false })
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -3,11 +3,11 @@ import { T } from 'Components'
|
|||
import CompanyDetails from 'Components/CompanyDetails'
|
||||
import React, { useCallback, useMemo, useState } from 'react'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { searchDenominationOrSiren } from '../api/sirene'
|
||||
import { Etablissement, searchDenominationOrSiren } from '../api/sirene'
|
||||
import { debounce } from '../utils'
|
||||
|
||||
export default function Search() {
|
||||
const [searchResults, setSearchResults] = useState()
|
||||
const [searchResults, setSearchResults] = useState<Array<Etablissement>>()
|
||||
const [isLoading, setLoadingState] = useState(false)
|
||||
|
||||
const handleSearch = useCallback(
|
||||
|
@ -85,7 +85,8 @@ export default function Search() {
|
|||
:focus {
|
||||
background-color: var(--lighterColour);
|
||||
}
|
||||
`}>
|
||||
`}
|
||||
>
|
||||
<CompanyDetails siren={siren} denomination={denomination} />
|
||||
</button>
|
||||
))}
|
|
@ -43,7 +43,8 @@ export default function Newsletter() {
|
|||
onSubmit={onSubmit}
|
||||
id="mc-embedded-subscribe-form"
|
||||
name="mc-embedded-subscribe-form"
|
||||
target="_blank">
|
||||
target="_blank"
|
||||
>
|
||||
<div>
|
||||
<label htmlFor="mce-EMAIL">
|
||||
<T>Votre adresse e-mail</T>
|
||||
|
@ -53,7 +54,7 @@ export default function Newsletter() {
|
|||
<input
|
||||
className="ui__ plain small button"
|
||||
type="submit"
|
||||
value={t("S'inscrire")}
|
||||
value={t("S'inscrire") as string}
|
||||
name="subscribe"
|
||||
id="mc-embedded-subscribe"
|
||||
/>
|
|
@ -3,15 +3,24 @@ import React, { useEffect } from 'react'
|
|||
import * as animate from 'Ui/animate'
|
||||
import { LinkButton } from 'Ui/Button'
|
||||
import './Overlay.css'
|
||||
export default function Overlay({ onClose, children, ...otherProps }) {
|
||||
|
||||
type OverlayProps = React.HTMLAttributes<HTMLDivElement> & {
|
||||
onClose?: () => void
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export default function Overlay({
|
||||
onClose,
|
||||
children,
|
||||
...otherProps
|
||||
}: OverlayProps) {
|
||||
useEffect(() => {
|
||||
const body = document.getElementsByTagName('body')[0]
|
||||
body.classList.add('no-scroll');
|
||||
body.classList.add('no-scroll')
|
||||
return () => {
|
||||
body.classList.remove('no-scroll')
|
||||
}
|
||||
}
|
||||
, [])
|
||||
}, [])
|
||||
return (
|
||||
<div id="overlayWrapper">
|
||||
<animate.fromBottom>
|
||||
|
@ -19,18 +28,21 @@ export default function Overlay({ onClose, children, ...otherProps }) {
|
|||
focusTrapOptions={{
|
||||
onDeactivate: onClose,
|
||||
clickOutsideDeactivates: !!onClose
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<div
|
||||
aria-modal="true"
|
||||
id="overlayContent"
|
||||
{...otherProps}
|
||||
className={'ui__ card ' + otherProps.className}>
|
||||
className={'ui__ card ' + otherProps?.className}
|
||||
>
|
||||
{children}
|
||||
{onClose && (
|
||||
<LinkButton
|
||||
aria-label="close"
|
||||
onClick={onClose}
|
||||
id="overlayCloseButton">
|
||||
id="overlayCloseButton"
|
||||
>
|
||||
×
|
||||
</LinkButton>
|
||||
)}
|
|
@ -2,6 +2,7 @@ import { updatePeriod } from 'Actions/actions'
|
|||
import React from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { RootState } from 'Reducers/rootReducer'
|
||||
import { situationSelector } from 'Selectors/analyseSelectors'
|
||||
import './PeriodSwitch.css'
|
||||
|
||||
|
@ -9,7 +10,8 @@ export default function PeriodSwitch() {
|
|||
const dispatch = useDispatch()
|
||||
const situation = useSelector(situationSelector)
|
||||
const defaultPeriod = useSelector(
|
||||
state => state.simulation?.config?.situation?.période || 'année'
|
||||
(state: RootState) =>
|
||||
state.simulation?.config?.situation?.période || 'année'
|
||||
)
|
||||
const currentPeriod = situation.période
|
||||
let periods = ['année', 'mois']
|
|
@ -1,54 +0,0 @@
|
|||
import {
|
||||
deletePreviousSimulation,
|
||||
loadPreviousSimulation
|
||||
} from 'Actions/actions'
|
||||
import { compose } from 'ramda'
|
||||
import React from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { connect } from 'react-redux'
|
||||
import { noUserInputSelector } from 'Selectors/analyseSelectors'
|
||||
import { LinkButton } from 'Ui/Button'
|
||||
import Banner from './Banner'
|
||||
|
||||
import type { SavedSimulation } from 'Types/State'
|
||||
|
||||
type ConnectedPropTypes = {
|
||||
previousSimulation: SavedSimulation,
|
||||
loadPreviousSimulation: () => void,
|
||||
newSimulationStarted: boolean,
|
||||
deletePreviousSimulation: () => void
|
||||
}
|
||||
const PreviousSimulationBanner = ({
|
||||
previousSimulation,
|
||||
deletePreviousSimulation,
|
||||
newSimulationStarted,
|
||||
loadPreviousSimulation
|
||||
}: ConnectedPropTypes) => (
|
||||
<Banner hidden={!previousSimulation || newSimulationStarted} icon="💾">
|
||||
<Trans i18nKey="previousSimulationBanner.info">
|
||||
Votre précédente simulation a été sauvegardée.
|
||||
</Trans>{' '}
|
||||
<LinkButton onClick={loadPreviousSimulation}>
|
||||
<Trans i18nKey="previousSimulationBanner.retrieveButton">
|
||||
Retrouver ma simulation
|
||||
</Trans>
|
||||
</LinkButton>
|
||||
.{' '}
|
||||
<LinkButton onClick={deletePreviousSimulation}>
|
||||
<Trans>Effacer</Trans>
|
||||
</LinkButton>
|
||||
</Banner>
|
||||
)
|
||||
|
||||
export default compose(
|
||||
connect(
|
||||
state => ({
|
||||
previousSimulation: state.previousSimulation,
|
||||
newSimulationStarted: !noUserInputSelector(state)
|
||||
}),
|
||||
{
|
||||
loadPreviousSimulation,
|
||||
deletePreviousSimulation
|
||||
}
|
||||
)
|
||||
)(PreviousSimulationBanner)
|
|
@ -0,0 +1,36 @@
|
|||
import {
|
||||
deletePreviousSimulation,
|
||||
loadPreviousSimulation
|
||||
} from 'Actions/actions'
|
||||
import React from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { RootState } from 'Reducers/rootReducer'
|
||||
import { noUserInputSelector } from 'Selectors/analyseSelectors'
|
||||
import { LinkButton } from 'Ui/Button'
|
||||
import Banner from './Banner'
|
||||
|
||||
export default function PreviousSimulationBanner() {
|
||||
const previousSimulation = useSelector(
|
||||
(state: RootState) => state.previousSimulation
|
||||
)
|
||||
const newSimulationStarted = !useSelector(noUserInputSelector)
|
||||
const dispatch = useDispatch()
|
||||
|
||||
return (
|
||||
<Banner hidden={!previousSimulation || newSimulationStarted} icon="💾">
|
||||
<Trans i18nKey="previousSimulationBanner.info">
|
||||
Votre précédente simulation a été sauvegardée.
|
||||
</Trans>{' '}
|
||||
<LinkButton onClick={() => dispatch(loadPreviousSimulation())}>
|
||||
<Trans i18nKey="previousSimulationBanner.retrieveButton">
|
||||
Retrouver ma simulation
|
||||
</Trans>
|
||||
</LinkButton>
|
||||
.{' '}
|
||||
<LinkButton onClick={() => dispatch(deletePreviousSimulation())}>
|
||||
<Trans>Effacer</Trans>
|
||||
</LinkButton>
|
||||
</Banner>
|
||||
)
|
||||
}
|
|
@ -1,72 +0,0 @@
|
|||
import { goToQuestion } from 'Actions/actions'
|
||||
import { T } from 'Components'
|
||||
import { compose, contains, filter, reject, toPairs } from 'ramda'
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import {
|
||||
currentQuestionSelector,
|
||||
nextStepsSelector
|
||||
} from 'Selectors/analyseSelectors'
|
||||
import type { Location } from 'react-router'
|
||||
|
||||
type OwnProps = {
|
||||
quickLinks: { [string]: string }
|
||||
}
|
||||
type Props = OwnProps & {
|
||||
goToQuestion: string => void,
|
||||
location: Location,
|
||||
currentQuestion: string,
|
||||
nextSteps: Array<string>,
|
||||
quickLinksToHide: Array<string>,
|
||||
show: boolean
|
||||
}
|
||||
|
||||
const QuickLinks = ({
|
||||
goToQuestion,
|
||||
currentQuestion,
|
||||
nextSteps,
|
||||
quickLinks,
|
||||
quickLinksToHide
|
||||
}: Props) => {
|
||||
if (!quickLinks) {
|
||||
return null
|
||||
}
|
||||
const links = compose(
|
||||
toPairs,
|
||||
filter(dottedName => contains(dottedName, nextSteps)),
|
||||
reject(dottedName => contains(dottedName, quickLinksToHide))
|
||||
)(quickLinks)
|
||||
|
||||
return (
|
||||
!!links.length && (
|
||||
<span>
|
||||
<small>Questions :</small>
|
||||
{links.map(([label, dottedName]) => (
|
||||
<button
|
||||
key={dottedName}
|
||||
className={`ui__ link-button ${
|
||||
dottedName === currentQuestion ? 'active' : ''
|
||||
}`}
|
||||
css="margin: 0 0.4rem !important"
|
||||
onClick={() => goToQuestion(dottedName)}>
|
||||
<T k={'quicklinks.' + label}>{label}</T>
|
||||
</button>
|
||||
))}{' '}
|
||||
{/* <button className="ui__ link-button">Voir la liste</button> */}
|
||||
</span>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
export default connect(
|
||||
(state, props) => ({
|
||||
key: props.language,
|
||||
currentQuestion: currentQuestionSelector(state),
|
||||
nextSteps: nextStepsSelector(state),
|
||||
quickLinks: state.simulation?.config.questions?.["à l'affiche"],
|
||||
quickLinksToHide: state.conversationSteps.foldedSteps
|
||||
}),
|
||||
{
|
||||
goToQuestion
|
||||
}
|
||||
)(QuickLinks)
|
|
@ -0,0 +1,52 @@
|
|||
import { goToQuestion } from 'Actions/actions'
|
||||
import { T } from 'Components'
|
||||
import { compose, contains, filter, reject, toPairs } from 'ramda'
|
||||
import React from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { RootState } from 'Reducers/rootReducer'
|
||||
import {
|
||||
currentQuestionSelector,
|
||||
nextStepsSelector
|
||||
} from 'Selectors/analyseSelectors'
|
||||
|
||||
export default function QuickLinks() {
|
||||
const currentQuestion = useSelector(currentQuestionSelector)
|
||||
const nextSteps = useSelector(nextStepsSelector)
|
||||
const quickLinks = useSelector(
|
||||
(state: RootState) => state.simulation?.config.questions?.["à l'affiche"]
|
||||
)
|
||||
const quickLinksToHide = useSelector(
|
||||
(state: RootState) => state.conversationSteps.foldedSteps
|
||||
)
|
||||
const dispatch = useDispatch()
|
||||
|
||||
if (!quickLinks) {
|
||||
return null
|
||||
}
|
||||
const links = compose(
|
||||
toPairs,
|
||||
filter(dottedName => contains(dottedName, nextSteps)) as any,
|
||||
reject(dottedName => contains(dottedName, quickLinksToHide))
|
||||
)(quickLinks) as any
|
||||
|
||||
return (
|
||||
!!links.length && (
|
||||
<span>
|
||||
<small>Questions :</small>
|
||||
{links.map(([label, dottedName]) => (
|
||||
<button
|
||||
key={dottedName}
|
||||
className={`ui__ link-button ${
|
||||
dottedName === currentQuestion ? 'active' : ''
|
||||
}`}
|
||||
css="margin: 0 0.4rem !important"
|
||||
onClick={() => dispatch(goToQuestion(dottedName))}
|
||||
>
|
||||
<T k={'quicklinks.' + label}>{label}</T>
|
||||
</button>
|
||||
))}{' '}
|
||||
{/* <button className="ui__ link-button">Voir la liste</button> */}
|
||||
</span>
|
||||
)
|
||||
)
|
||||
}
|
|
@ -3,11 +3,11 @@ import { SitePathsContext } from 'Components/utils/withSitePaths'
|
|||
import { encodeRuleName, nameLeaf } from 'Engine/rules'
|
||||
import React, { useContext } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Règle } from 'Types/RegleTypes'
|
||||
import { Rule } from 'Types/rule'
|
||||
import './RuleLink.css'
|
||||
|
||||
type RuleLinkProps = Règle & {
|
||||
style: React.CSSProperties
|
||||
type RuleLinkProps = Rule & {
|
||||
style?: React.CSSProperties
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,8 @@ export default function RuleLink({
|
|||
to={newPath}
|
||||
className="rule-link"
|
||||
title={title}
|
||||
style={{ color: colour, ...style }}>
|
||||
style={{ color: colour, ...style }}
|
||||
>
|
||||
{children || title || nameLeaf(dottedName)}
|
||||
</Link>
|
||||
)
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
import { goBackToSimulation } from 'Actions/actions'
|
||||
import { ScrollToTop } from 'Components/utils/Scroll'
|
||||
import { decodeRuleName, findRuleByDottedName } from 'Engine/rules.js'
|
||||
import { compose } from 'ramda'
|
||||
import React from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { connect } from 'react-redux'
|
||||
import { connect, useSelector } from 'react-redux'
|
||||
import { Redirect } from 'react-router-dom'
|
||||
import { flatRulesSelector, noUserInputSelector, situationBranchNameSelector } from 'Selectors/analyseSelectors'
|
||||
import {
|
||||
flatRulesSelector,
|
||||
noUserInputSelector,
|
||||
situationBranchNameSelector
|
||||
} from 'Selectors/analyseSelectors'
|
||||
import { DottedName } from 'Types/rule'
|
||||
import Rule from './rule/Rule'
|
||||
import './RulePage.css'
|
||||
import SearchButton from './SearchButton'
|
||||
|
||||
|
||||
export default compose(
|
||||
connect(state => ({
|
||||
valuesToShow: !noUserInputSelector(state),
|
||||
flatRules: flatRulesSelector(state),
|
||||
brancheName: situationBranchNameSelector(state)
|
||||
}))
|
||||
)(function RulePage({ flatRules, match, valuesToShow, brancheName }) {
|
||||
let name = match ?.params ?.name,
|
||||
export default function RulePage({ match }) {
|
||||
const flatRules = useSelector(flatRulesSelector)
|
||||
const brancheName = useSelector(situationBranchNameSelector)
|
||||
const valuesToShow = !useSelector(noUserInputSelector)
|
||||
let name = match?.params?.name,
|
||||
decodedRuleName = decodeRuleName(name)
|
||||
|
||||
const renderRule = dottedName => {
|
||||
const renderRule = (dottedName: DottedName) => {
|
||||
return (
|
||||
<div id="RulePage">
|
||||
<ScrollToTop key={brancheName + dottedName} />
|
||||
|
@ -40,20 +40,16 @@ export default compose(
|
|||
return <Redirect to="/404" />
|
||||
|
||||
return renderRule(decodedRuleName)
|
||||
})
|
||||
}
|
||||
|
||||
const BackToSimulation = compose(
|
||||
connect(
|
||||
null,
|
||||
{ goBackToSimulation }
|
||||
)
|
||||
)(
|
||||
const BackToSimulation = connect(null, { goBackToSimulation })(
|
||||
// Triggers rerender when the language changes
|
||||
function BackToSimulation({ goBackToSimulation }) {
|
||||
return (
|
||||
<button
|
||||
className="ui__ simple small push-left button"
|
||||
onClick={goBackToSimulation}>
|
||||
onClick={goBackToSimulation}
|
||||
>
|
||||
← <Trans i18nKey="back">Reprendre la simulation</Trans>
|
||||
</button>
|
||||
)
|
|
@ -4,11 +4,11 @@ import PaySlip from 'Components/PaySlip'
|
|||
import StackedBarChart from 'Components/StackedBarChart'
|
||||
import { ThemeColoursContext } from 'Components/utils/withColours'
|
||||
import { getRuleFromAnalysis } from 'Engine/rules'
|
||||
import React, { useRef, useContext } from 'react'
|
||||
import React, { useContext, useRef } from 'react'
|
||||
import emoji from 'react-easy-emoji'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RootState } from 'Reducers/rootReducer'
|
||||
import {
|
||||
analysisWithDefaultsSelector,
|
||||
usePeriod
|
||||
|
@ -16,7 +16,7 @@ import {
|
|||
import * as Animate from 'Ui/animate'
|
||||
|
||||
class ErrorBoundary extends React.Component {
|
||||
state = {}
|
||||
state = {} as { error?: string }
|
||||
static getDerivedStateFromError() {
|
||||
return {
|
||||
error:
|
||||
|
@ -32,12 +32,12 @@ class ErrorBoundary extends React.Component {
|
|||
|
||||
export default function SalaryExplanation() {
|
||||
const showDistributionFirst = useSelector(
|
||||
state => !state.conversationSteps.foldedSteps.length
|
||||
(state: RootState) => !state.conversationSteps.foldedSteps.length
|
||||
)
|
||||
const distributionRef = useRef({})
|
||||
const distributionRef = useRef<HTMLDivElement>()
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<Animate.fromTop key={showDistributionFirst}>
|
||||
<Animate.fromTop key={showDistributionFirst.toString()}>
|
||||
{showDistributionFirst ? (
|
||||
<>
|
||||
<RevenueRepatitionSection />
|
||||
|
@ -55,7 +55,8 @@ export default function SalaryExplanation() {
|
|||
behavior: 'smooth',
|
||||
block: 'start'
|
||||
})
|
||||
}>
|
||||
}
|
||||
>
|
||||
{emoji('📊')} <T>Voir la répartition des cotisations</T>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -71,9 +72,7 @@ export default function SalaryExplanation() {
|
|||
Le simulateur vous aide à comprendre votre bulletin de paie, sans
|
||||
lui être opposable. Pour plus d'informations, rendez vous
|
||||
sur
|
||||
<a
|
||||
alt="service-public.fr"
|
||||
href="https://www.service-public.fr/particuliers/vosdroits/F559">
|
||||
<a href="https://www.service-public.fr/particuliers/vosdroits/F559">
|
||||
service-public.fr
|
||||
</a>
|
||||
.
|
|
@ -8,57 +8,55 @@ import { T } from 'Components'
|
|||
import Conversation from 'Components/conversation/Conversation'
|
||||
import SeeAnswersButton from 'Components/conversation/SeeAnswersButton'
|
||||
import PeriodSwitch from 'Components/PeriodSwitch'
|
||||
// $FlowFixMe
|
||||
import ComparaisonConfig from 'Components/simulationConfigs/rémunération-dirigeant.yaml'
|
||||
import withSimulationConfig from 'Components/simulationConfigs/withSimulationConfig'
|
||||
import withSitePaths from 'Components/utils/withSitePaths'
|
||||
import { useSimulationConfig } from 'Components/simulationConfigs/useSimulationConfig'
|
||||
import { SitePathsContext } from 'Components/utils/withSitePaths'
|
||||
import Value from 'Components/Value'
|
||||
import { encodeRuleName, getRuleFromAnalysis } from 'Engine/rules.js'
|
||||
import revenusSVG from 'Images/revenus.svg'
|
||||
import { compose } from 'ramda'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import React, { useCallback, useContext, useState } from 'react'
|
||||
import emoji from 'react-easy-emoji'
|
||||
import { connect } from 'react-redux'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { RootState } from 'Reducers/rootReducer'
|
||||
import {
|
||||
analysisWithDefaultsSelector,
|
||||
branchAnalyseSelector
|
||||
} from 'Selectors/analyseSelectors'
|
||||
import { DottedName } from 'Types/rule'
|
||||
import Animate from 'Ui/animate'
|
||||
import InfoBulle from 'Ui/InfoBulle'
|
||||
import './SchemeComparaison.css'
|
||||
|
||||
type OwnProps = {
|
||||
hideAutoEntrepreneur?: boolean,
|
||||
hideAssimiléSalarié?: boolean
|
||||
}
|
||||
|
||||
type Props = OwnProps & {
|
||||
setSituationBranch: number => void,
|
||||
defineDirectorStatus: string => void,
|
||||
sitePaths: any,
|
||||
isAutoentrepreneur: boolean => void,
|
||||
plafondAutoEntrepreneurDépassé: boolean
|
||||
}
|
||||
|
||||
let getBranchIndex = branch =>
|
||||
let getBranchIndex = (branch: string) =>
|
||||
({ assimilé: 0, indépendant: 1, 'auto-entrepreneur': 2 }[branch])
|
||||
let getRuleFrom = analyses => (branch, dottedName) => {
|
||||
|
||||
let getRuleFrom = analyses => (branch: string, dottedName: DottedName) => {
|
||||
let i = getBranchIndex(branch)
|
||||
return getRuleFromAnalysis(analyses[i])(dottedName)
|
||||
}
|
||||
|
||||
const SchemeComparaison = ({
|
||||
/* Own Props */
|
||||
hideAutoEntrepreneur = false,
|
||||
hideAssimiléSalarié = false,
|
||||
/* Injected Props */
|
||||
type SchemeComparaisonProps = {
|
||||
hideAutoEntrepreneur?: boolean
|
||||
hideAssimiléSalarié?: boolean
|
||||
}
|
||||
|
||||
export default function SchemeComparaison({
|
||||
hideAutoEntrepreneur = false,
|
||||
hideAssimiléSalarié = false
|
||||
}: SchemeComparaisonProps) {
|
||||
useSimulationConfig(ComparaisonConfig)
|
||||
const dispatch = useDispatch()
|
||||
const analyses = useSelector(analysisWithDefaultsSelector)
|
||||
const plafondAutoEntrepreneurDépassé = useSelector((state: RootState) =>
|
||||
branchAnalyseSelector(state, {
|
||||
situationBranchName: 'Auto-entrepreneur'
|
||||
}).controls?.find(
|
||||
({ test }) =>
|
||||
test.includes && test.includes('base des cotisations > plafond')
|
||||
)
|
||||
)
|
||||
|
||||
plafondAutoEntrepreneurDépassé,
|
||||
defineDirectorStatus,
|
||||
isAutoentrepreneur,
|
||||
analyses
|
||||
}: Props) => {
|
||||
let getRule = getRuleFrom(analyses)
|
||||
const [showMore, setShowMore] = useState(false)
|
||||
const [conversationStarted, setConversationStarted] = useState(
|
||||
|
@ -74,7 +72,8 @@ const SchemeComparaison = ({
|
|||
className={classnames('comparaison-grid', {
|
||||
hideAutoEntrepreneur,
|
||||
hideAssimiléSalarié
|
||||
})}>
|
||||
})}
|
||||
>
|
||||
<h2 className="AS">
|
||||
{emoji('☂')} <T>Assimilé salarié</T>
|
||||
<small>
|
||||
|
@ -289,7 +288,8 @@ const SchemeComparaison = ({
|
|||
<div className="all">
|
||||
<button
|
||||
onClick={() => setShowMore(true)}
|
||||
className="ui__ simple small button">
|
||||
className="ui__ simple small button"
|
||||
>
|
||||
Afficher plus d'informations
|
||||
</button>
|
||||
</div>
|
||||
|
@ -315,7 +315,8 @@ const SchemeComparaison = ({
|
|||
<img src={revenusSVG} css="height: 8rem" />
|
||||
<button
|
||||
className="ui__ cta plain button"
|
||||
onClick={startConversation}>
|
||||
onClick={startConversation}
|
||||
>
|
||||
Lancer la simulation
|
||||
</button>
|
||||
</T>
|
||||
|
@ -354,7 +355,8 @@ const SchemeComparaison = ({
|
|||
className={classnames(
|
||||
'ui__ plain card',
|
||||
plafondAutoEntrepreneurDépassé && 'disabled'
|
||||
)}>
|
||||
)}
|
||||
>
|
||||
{plafondAutoEntrepreneurDépassé ? (
|
||||
'Plafond de CA dépassé'
|
||||
) : (
|
||||
|
@ -563,18 +565,21 @@ const SchemeComparaison = ({
|
|||
<button
|
||||
className="ui__ button"
|
||||
onClick={() => {
|
||||
defineDirectorStatus('SALARIED')
|
||||
!hideAutoEntrepreneur && isAutoentrepreneur(false)
|
||||
}}>
|
||||
dispatch(defineDirectorStatus('SALARIED'))
|
||||
!hideAutoEntrepreneur && dispatch(isAutoentrepreneur(false))
|
||||
}}
|
||||
>
|
||||
<T k="comparaisonRégimes.choix.AS">Assimilé salarié</T>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className="ui__ button"
|
||||
onClick={() => {
|
||||
!hideAssimiléSalarié && defineDirectorStatus('SELF_EMPLOYED')
|
||||
!hideAutoEntrepreneur && isAutoentrepreneur(false)
|
||||
}}>
|
||||
!hideAssimiléSalarié &&
|
||||
dispatch(defineDirectorStatus('SELF_EMPLOYED'))
|
||||
!hideAutoEntrepreneur && dispatch(isAutoentrepreneur(false))
|
||||
}}
|
||||
>
|
||||
{hideAssimiléSalarié ? (
|
||||
<T k="comparaisonRégimes.choix.EI">Entreprise individuelle</T>
|
||||
) : (
|
||||
|
@ -585,9 +590,11 @@ const SchemeComparaison = ({
|
|||
<button
|
||||
className="ui__ button"
|
||||
onClick={() => {
|
||||
!hideAssimiléSalarié && defineDirectorStatus('SELF_EMPLOYED')
|
||||
isAutoentrepreneur(true)
|
||||
}}>
|
||||
!hideAssimiléSalarié &&
|
||||
dispatch(defineDirectorStatus('SELF_EMPLOYED'))
|
||||
dispatch(isAutoentrepreneur(true))
|
||||
}}
|
||||
>
|
||||
<T k="comparaisonRégimes.choix.auto">Auto-entrepreneur</T>
|
||||
</button>
|
||||
)}
|
||||
|
@ -597,63 +604,37 @@ const SchemeComparaison = ({
|
|||
)
|
||||
}
|
||||
|
||||
const RuleValueLink = compose(
|
||||
withSitePaths,
|
||||
connect(
|
||||
state => ({
|
||||
analyses: analysisWithDefaultsSelector(state)
|
||||
}),
|
||||
{
|
||||
setSituationBranch
|
||||
}
|
||||
)
|
||||
)(
|
||||
({
|
||||
analyses,
|
||||
branch,
|
||||
rule: dottedName,
|
||||
sitePaths,
|
||||
appendText,
|
||||
setSituationBranch,
|
||||
unit
|
||||
}) => {
|
||||
let rule = getRuleFrom(analyses)(branch, dottedName)
|
||||
return !rule ? null : (
|
||||
<Link
|
||||
onClick={() => setSituationBranch(getBranchIndex(branch))}
|
||||
to={
|
||||
sitePaths.documentation.index + '/' + encodeRuleName(rule.dottedName)
|
||||
}>
|
||||
<Value
|
||||
maximumFractionDigits={0}
|
||||
{...rule}
|
||||
unit={
|
||||
/* //TODO the unit should be integrated in the leaf rules of base.yaml and infered by mecanisms. Will be done in a future release*/
|
||||
unit !== undefined ? unit : '€'
|
||||
}
|
||||
/>
|
||||
{appendText && <> {appendText}</>}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
)
|
||||
type RuleValueLinkProps = {
|
||||
branch: string
|
||||
rule: string
|
||||
appendText?: React.ReactNode
|
||||
unit?: null | string
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withSimulationConfig(ComparaisonConfig),
|
||||
connect(
|
||||
state => ({
|
||||
analyses: analysisWithDefaultsSelector(state),
|
||||
plafondAutoEntrepreneurDépassé: branchAnalyseSelector(state, {
|
||||
situationBranchName: 'Auto-entrepreneur'
|
||||
}).controls?.find(
|
||||
({ test }) =>
|
||||
test.includes && test.includes('base des cotisations > plafond')
|
||||
)
|
||||
}),
|
||||
{
|
||||
defineDirectorStatus,
|
||||
isAutoentrepreneur,
|
||||
setSituationBranch
|
||||
}
|
||||
function RuleValueLink({
|
||||
branch,
|
||||
rule: dottedName,
|
||||
appendText,
|
||||
unit
|
||||
}: RuleValueLinkProps) {
|
||||
const dispatch = useDispatch()
|
||||
const analyses = useSelector(analysisWithDefaultsSelector)
|
||||
const sitePaths = useContext(SitePathsContext)
|
||||
let rule = getRuleFrom(analyses)(branch, dottedName)
|
||||
return !rule ? null : (
|
||||
<Link
|
||||
onClick={() => dispatch(setSituationBranch(getBranchIndex(branch)))}
|
||||
to={sitePaths.documentation.index + '/' + encodeRuleName(rule.dottedName)}
|
||||
>
|
||||
<Value
|
||||
maximumFractionDigits={0}
|
||||
{...rule}
|
||||
unit={
|
||||
/* //TODO the unit should be integrated in the leaf rules of base.yaml and infered by mecanisms. Will be done in a future release*/
|
||||
unit !== undefined ? unit : '€'
|
||||
}
|
||||
/>
|
||||
{appendText && <> {appendText}</>}
|
||||
</Link>
|
||||
)
|
||||
)(SchemeComparaison)
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import withSitePaths from 'Components/utils/withSitePaths'
|
||||
import { SitePathsContext } from 'Components/utils/withSitePaths'
|
||||
import { encodeRuleName, parentName } from 'Engine/rules.js'
|
||||
import { compose, pick, sortBy, take } from 'ramda'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { pick, sortBy, take } from 'ramda'
|
||||
import React, { useContext, useEffect, useState } from 'react'
|
||||
import Highlighter from 'react-highlight-words'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Link, Redirect } from 'react-router-dom'
|
||||
|
@ -10,12 +10,12 @@ import { capitalise0 } from '../utils'
|
|||
|
||||
const worker = new Worker()
|
||||
|
||||
let SearchBar = ({
|
||||
export default function SearchBar({
|
||||
rules,
|
||||
showDefaultList,
|
||||
finally: finallyCallback,
|
||||
sitePaths
|
||||
}) => {
|
||||
finally: finallyCallback
|
||||
}) {
|
||||
const sitePaths = useContext(SitePathsContext)
|
||||
const [input, setInput] = useState('')
|
||||
const [selectedOption, setSelectedOption] = useState(null)
|
||||
const [results, setResults] = useState([])
|
||||
|
@ -29,7 +29,7 @@ let SearchBar = ({
|
|||
})
|
||||
|
||||
worker.onmessage = ({ data: results }) => setResults(results)
|
||||
}, [])
|
||||
}, [rules])
|
||||
|
||||
let renderOptions = rules => {
|
||||
let options =
|
||||
|
@ -120,5 +120,3 @@ let SearchBar = ({
|
|||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default compose(withSitePaths)(SearchBar)
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
import { compose } from 'ramda'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import emoji from 'react-easy-emoji'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { connect } from 'react-redux'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { flatRulesSelector } from 'Selectors/analyseSelectors'
|
||||
import Overlay from './Overlay'
|
||||
import SearchBar from './SearchBar'
|
||||
|
||||
export default compose(
|
||||
connect(state => ({
|
||||
flatRules: flatRulesSelector(state)
|
||||
}))
|
||||
)(function SearchButton({ flatRules, invisibleButton }) {
|
||||
type SearchButtonProps = {
|
||||
invisibleButton?: boolean
|
||||
}
|
||||
|
||||
export default function SearchButton({ invisibleButton }: SearchButtonProps) {
|
||||
const flatRules = useSelector(flatRulesSelector)
|
||||
const [visible, setVisible] = useState(false)
|
||||
useEffect(() => {
|
||||
const handleKeyDown = e => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (!(e.ctrlKey && e.key === 'k')) return
|
||||
setVisible(true)
|
||||
e.preventDefault()
|
||||
|
@ -40,8 +40,9 @@ export default compose(
|
|||
) : invisibleButton ? null : (
|
||||
<button
|
||||
className="ui__ simple small button"
|
||||
onClick={() => setVisible(true)}>
|
||||
onClick={() => setVisible(true)}
|
||||
>
|
||||
{emoji('🔍')} <Trans>Rechercher</Trans>
|
||||
</button>
|
||||
)
|
||||
})
|
||||
}
|
|
@ -3,7 +3,13 @@ import { usePersistingState } from 'Components/utils/persistState'
|
|||
import React from 'react'
|
||||
import emoji from 'react-easy-emoji'
|
||||
|
||||
export default function SimulateurWarning({ simulateur }) {
|
||||
type SimulateurWarningProps = {
|
||||
simulateur: string
|
||||
}
|
||||
|
||||
export default function SimulateurWarning({
|
||||
simulateur
|
||||
}: SimulateurWarningProps) {
|
||||
let [folded, fold] = usePersistingState(
|
||||
'app::simulateurs:warning-folded:v1:' + simulateur,
|
||||
false
|
||||
|
@ -13,7 +19,8 @@ export default function SimulateurWarning({ simulateur }) {
|
|||
id="SimulateurWarning"
|
||||
css={`
|
||||
margin-bottom: 1rem;
|
||||
`}>
|
||||
`}
|
||||
>
|
||||
<p>
|
||||
{emoji('🚩 ')}
|
||||
<strong>
|
||||
|
@ -22,7 +29,8 @@ export default function SimulateurWarning({ simulateur }) {
|
|||
{folded && (
|
||||
<button
|
||||
className="ui__ button simple small"
|
||||
onClick={() => fold(false)}>
|
||||
onClick={() => fold(false)}
|
||||
>
|
||||
<T k="simulateurs.warning.plus">Lire les précisions</T>
|
||||
</button>
|
||||
)}
|
||||
|
@ -30,7 +38,8 @@ export default function SimulateurWarning({ simulateur }) {
|
|||
{!folded && (
|
||||
<div
|
||||
className="ui__ card light-bg"
|
||||
css="padding-top: 1rem; padding-bottom: 0.4rem">
|
||||
css="padding-top: 1rem; padding-bottom: 0.4rem"
|
||||
>
|
||||
<ul>
|
||||
{simulateur == 'auto-entreprise' && (
|
||||
<li>
|
||||
|
@ -66,7 +75,8 @@ export default function SimulateurWarning({ simulateur }) {
|
|||
<div className="ui__ answer-group">
|
||||
<button
|
||||
className="ui__ button simple small"
|
||||
onClick={() => fold(true)}>
|
||||
onClick={() => fold(true)}
|
||||
>
|
||||
<T>J'ai compris</T>
|
||||
</button>
|
||||
</div>
|
|
@ -1,6 +1,8 @@
|
|||
import { T } from 'Components'
|
||||
import Controls from 'Components/Controls'
|
||||
import Conversation from 'Components/conversation/Conversation'
|
||||
import Conversation, {
|
||||
ConversationProps
|
||||
} from 'Components/conversation/Conversation'
|
||||
import SeeAnswersButton from 'Components/conversation/SeeAnswersButton'
|
||||
import PageFeedback from 'Components/Feedback/PageFeedback'
|
||||
import SearchButton from 'Components/SearchButton'
|
||||
|
@ -12,7 +14,15 @@ import { simulationProgressSelector } from 'Selectors/progressSelectors'
|
|||
import * as Animate from 'Ui/animate'
|
||||
import Progress from 'Ui/Progress'
|
||||
|
||||
export default function Simulation({ explanations, customEndMessages }) {
|
||||
type SimulationProps = {
|
||||
explanations: React.ReactNode
|
||||
customEndMessages?: ConversationProps['customEndMessages']
|
||||
}
|
||||
|
||||
export default function Simulation({
|
||||
explanations,
|
||||
customEndMessages
|
||||
}: SimulationProps) {
|
||||
const firstStepCompleted = useSelector(firstStepCompletedSelector)
|
||||
const progress = useSelector(simulationProgressSelector)
|
||||
return (
|
||||
|
@ -29,7 +39,8 @@ export default function Simulation({ explanations, customEndMessages }) {
|
|||
marginTop: '1.2rem',
|
||||
marginBottom: '0.6rem',
|
||||
alignItems: 'baseline'
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
{progress < 1 ? (
|
||||
<small css="padding: 0.4rem 0">
|
||||
<T k="simulateurs.précision.défaut">
|
||||
|
@ -37,8 +48,8 @@ export default function Simulation({ explanations, customEndMessages }) {
|
|||
</T>
|
||||
</small>
|
||||
) : (
|
||||
<span />
|
||||
)}
|
||||
<span />
|
||||
)}
|
||||
<SeeAnswersButton />
|
||||
</div>
|
||||
<section className="ui__ full-width lighter-bg">
|
|
@ -1,8 +1,9 @@
|
|||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import RuleLink from 'Components/RuleLink'
|
||||
import useDisplayOnIntersecting from 'Components/utils/useDisplayOnIntersecting'
|
||||
import React from 'react'
|
||||
import { animated, useSpring } from 'react-spring'
|
||||
import styled from 'styled-components'
|
||||
import { Rule } from 'Types/rule'
|
||||
import { capitalise0 } from '../utils'
|
||||
|
||||
const BarStack = styled.div`
|
||||
|
@ -51,7 +52,7 @@ const SmallCircle = styled.span`
|
|||
border-radius: 100%;
|
||||
`
|
||||
|
||||
function integerAndDecimalParts(value) {
|
||||
function integerAndDecimalParts(value: number) {
|
||||
const integer = Math.floor(value)
|
||||
const decimal = value - integer
|
||||
return { integer, decimal }
|
||||
|
@ -59,8 +60,8 @@ function integerAndDecimalParts(value) {
|
|||
|
||||
// This function calculates rounded percentages so that the sum of all
|
||||
// returned values is always 100. For instance: [60, 30, 10].
|
||||
export function roundedPercentages(values) {
|
||||
const sum = (a = 0, b) => a + b
|
||||
export function roundedPercentages(values: Array<number>) {
|
||||
const sum = (a: number = 0, b: number) => a + b
|
||||
const total = values.reduce(sum)
|
||||
const percentages = values.map(value =>
|
||||
integerAndDecimalParts((value / total) * 100)
|
||||
|
@ -78,7 +79,11 @@ export function roundedPercentages(values) {
|
|||
)
|
||||
}
|
||||
|
||||
export default function StackedBarChart({ data }) {
|
||||
type StackedBarChartProps = {
|
||||
data: Array<{ color?: string } & Rule>
|
||||
}
|
||||
|
||||
export default function StackedBarChart({ data }: StackedBarChartProps) {
|
||||
const [intersectionRef, displayChart] = useDisplayOnIntersecting({
|
||||
threshold: 0.5
|
||||
})
|
|
@ -1,10 +1,10 @@
|
|||
import { updateSituation } from 'Actions/actions'
|
||||
import { setActiveTarget, updateSituation } from 'Actions/actions'
|
||||
import { T } from 'Components'
|
||||
import InputSuggestions from 'Components/conversation/InputSuggestions'
|
||||
import PeriodSwitch from 'Components/PeriodSwitch'
|
||||
import RuleLink from 'Components/RuleLink'
|
||||
import { ThemeColoursContext } from 'Components/utils/withColours'
|
||||
import withSitePaths from 'Components/utils/withSitePaths'
|
||||
import { SitePathsContext } from 'Components/utils/withSitePaths'
|
||||
import { formatCurrency } from 'Engine/format'
|
||||
import { encodeRuleName } from 'Engine/rules'
|
||||
import { isEmpty, isNil } from 'ramda'
|
||||
|
@ -13,12 +13,13 @@ import emoji from 'react-easy-emoji'
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { RootState } from 'Reducers/rootReducer'
|
||||
import {
|
||||
analysisWithDefaultsSelector,
|
||||
useSituation,
|
||||
useSituationValue,
|
||||
situationSelector,
|
||||
useTarget
|
||||
} from 'Selectors/analyseSelectors'
|
||||
import { Rule } from 'Types/rule'
|
||||
import Animate from 'Ui/animate'
|
||||
import AnimatedTargetValue from 'Ui/AnimatedTargetValue'
|
||||
import CurrencyInput from './CurrencyInput/CurrencyInput'
|
||||
|
@ -28,12 +29,13 @@ export default function TargetSelection() {
|
|||
const [initialRender, setInitialRender] = useState(true)
|
||||
const analysis = useSelector(analysisWithDefaultsSelector)
|
||||
const objectifs = useSelector(
|
||||
state => state.simulation?.config.objectifs || []
|
||||
(state: RootState) => state.simulation?.config.objectifs || []
|
||||
)
|
||||
const secondaryObjectives = useSelector(
|
||||
state => state.simulation?.config['objectifs secondaires'] || []
|
||||
(state: RootState) =>
|
||||
state.simulation?.config['objectifs secondaires'] || []
|
||||
)
|
||||
const situation = useSituation()
|
||||
const situation = useSelector(situationSelector)
|
||||
const dispatch = useDispatch()
|
||||
const colours = useContext(ThemeColoursContext)
|
||||
|
||||
|
@ -73,7 +75,9 @@ export default function TargetSelection() {
|
|||
|
||||
return (
|
||||
<div id="targetSelection">
|
||||
{(typeof objectifs[0] === 'string' ? [{ objectifs }] : objectifs).map(
|
||||
{((typeof objectifs[0] === 'string'
|
||||
? [{ objectifs }]
|
||||
: objectifs) as any).map(
|
||||
({ icône, objectifs: groupTargets, nom }, index) => (
|
||||
<React.Fragment key={nom || '0'}>
|
||||
<div style={{ display: 'flex', alignItems: 'end' }}>
|
||||
|
@ -96,7 +100,8 @@ export default function TargetSelection() {
|
|||
${colours.darkColour} 0%,
|
||||
${colours.colour} 100%
|
||||
)`
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<Targets
|
||||
{...{
|
||||
targets: targets.filter(({ dottedName }) =>
|
||||
|
@ -138,7 +143,7 @@ let Targets = ({ targets, initialRender }) => (
|
|||
)
|
||||
|
||||
const Target = ({ target, initialRender }) => {
|
||||
const activeInput = useSelector(state => state.activeTargetInput)
|
||||
const activeInput = useSelector((state: RootState) => state.activeTargetInput)
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const isActiveInput = activeInput === target.dottedName
|
||||
|
@ -147,7 +152,8 @@ const Target = ({ target, initialRender }) => {
|
|||
return (
|
||||
<li
|
||||
key={target.name}
|
||||
className={isSmallTarget ? 'small-target' : undefined}>
|
||||
className={isSmallTarget ? 'small-target' : undefined}
|
||||
>
|
||||
<Animate.appear unless={initialRender}>
|
||||
<div>
|
||||
<div className="main">
|
||||
|
@ -192,7 +198,8 @@ const Target = ({ target, initialRender }) => {
|
|||
)
|
||||
}
|
||||
|
||||
let Header = withSitePaths(({ target, sitePaths }) => {
|
||||
let Header = ({ target }) => {
|
||||
const sitePaths = useContext(SitePathsContext)
|
||||
const ruleLink =
|
||||
sitePaths.documentation.index + '/' + encodeRuleName(target.dottedName)
|
||||
return (
|
||||
|
@ -205,26 +212,40 @@ let Header = withSitePaths(({ target, sitePaths }) => {
|
|||
</span>
|
||||
</span>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
let TargetInputOrValue = ({ target, isActiveInput, isSmallTarget }) => {
|
||||
type TargetInputOrValueProps = {
|
||||
target: Rule
|
||||
isActiveInput: boolean
|
||||
isSmallTarget: boolean
|
||||
}
|
||||
|
||||
let TargetInputOrValue = ({
|
||||
target,
|
||||
isActiveInput,
|
||||
isSmallTarget
|
||||
}: TargetInputOrValueProps) => {
|
||||
const { language } = useTranslation().i18n
|
||||
const colors = useContext(ThemeColoursContext)
|
||||
const dispatch = useDispatch()
|
||||
const situationValue = Math.round(useSituationValue(target.dottedName))
|
||||
const situationValue = Math.round(
|
||||
useSelector(situationSelector)[target.dottedName]
|
||||
)
|
||||
const targetWithValue = useTarget(target.dottedName)
|
||||
const value = targetWithValue?.nodeValue
|
||||
? Math.round(targetWithValue?.nodeValue)
|
||||
: undefined
|
||||
const inversionFail = useSelector(
|
||||
state => analysisWithDefaultsSelector(state)?.cache.inversionFail
|
||||
(state: RootState) =>
|
||||
analysisWithDefaultsSelector(state)?.cache.inversionFail
|
||||
)
|
||||
const blurValue = inversionFail && !isActiveInput && value
|
||||
|
||||
return (
|
||||
<span
|
||||
className="targetInputOrValue"
|
||||
style={blurValue ? { filter: 'blur(3px)' } : {}}>
|
||||
style={blurValue ? { filter: 'blur(3px)' } : {}}
|
||||
>
|
||||
{target.question ? (
|
||||
<>
|
||||
{!isActiveInput && <AnimatedTargetValue value={value} />}
|
||||
|
@ -246,10 +267,7 @@ let TargetInputOrValue = ({ target, isActiveInput, isSmallTarget }) => {
|
|||
}
|
||||
onFocus={() => {
|
||||
if (isSmallTarget) return
|
||||
dispatch({
|
||||
type: 'SET_ACTIVE_TARGET_INPUT',
|
||||
name: target.dottedName
|
||||
})
|
||||
dispatch(setActiveTarget(target.dottedName))
|
||||
}}
|
||||
language={language}
|
||||
/>
|
||||
|
@ -269,6 +287,7 @@ let TargetInputOrValue = ({ target, isActiveInput, isSmallTarget }) => {
|
|||
|
||||
function AidesGlimpse() {
|
||||
const aides = useTarget('contrat salarié . aides employeur')
|
||||
const { language } = useTranslation().i18n
|
||||
|
||||
// Dans le cas où il n'y a qu'une seule aide à l'embauche qui s'applique, nous
|
||||
// faisons un lien direct vers cette aide, plutôt qu'un lien vers la liste qui
|
||||
|
@ -286,7 +305,7 @@ function AidesGlimpse() {
|
|||
<T>en incluant</T>{' '}
|
||||
<strong>
|
||||
<AnimatedTargetValue value={aides.nodeValue}>
|
||||
<span>{formatCurrency(aides.nodeValue)}</span>
|
||||
<span>{formatCurrency(aides.nodeValue, language)}</span>
|
||||
</AnimatedTargetValue>
|
||||
</strong>{' '}
|
||||
<T>d'aides</T> {emoji(aides.explanation.icons)}
|
|
@ -1,17 +1,15 @@
|
|||
import withColours from 'Components/utils/withColours'
|
||||
import withSitePaths from 'Components/utils/withSitePaths'
|
||||
import { compose } from 'ramda'
|
||||
import React from 'react'
|
||||
import { ThemeColoursContext } from 'Components/utils/withColours'
|
||||
import { SitePathsContext } from 'Components/utils/withSitePaths'
|
||||
import React, { useContext } from 'react'
|
||||
import emoji from 'react-easy-emoji'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { analysisWithDefaultsSelector } from 'Selectors/analyseSelectors'
|
||||
import './Targets.css'
|
||||
|
||||
export default compose(
|
||||
withColours,
|
||||
withSitePaths
|
||||
)(function Targets({ colours, sitePaths }) {
|
||||
export default function Targets() {
|
||||
const colours = useContext(ThemeColoursContext)
|
||||
const sitePaths = useContext(SitePathsContext)
|
||||
const analysis = useSelector(analysisWithDefaultsSelector)
|
||||
let { nodeValue, unité: unit, dottedName } = analysis.targets[0]
|
||||
return (
|
||||
|
@ -26,10 +24,11 @@ export default compose(
|
|||
title="Quel est calcul ?"
|
||||
style={{ color: colours.colour }}
|
||||
to={sitePaths.documentation.index + '/' + dottedName}
|
||||
className="explanation">
|
||||
className="explanation"
|
||||
>
|
||||
{emoji('📖')}
|
||||
</Link>
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
import { T } from 'Components'
|
||||
import { formatValue } from 'Engine/format'
|
||||
import { formatValue, formatValueOptions } from 'Engine/format'
|
||||
import React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Rule } from 'Types/rule'
|
||||
|
||||
// let booleanTranslations = { true: '✅', false: '❌' }
|
||||
|
||||
|
@ -16,6 +17,19 @@ let style = customStyle => `
|
|||
${customStyle}
|
||||
`
|
||||
|
||||
type ValueProps = Partial<
|
||||
Pick<Rule, 'nodeValue' | 'unit'> &
|
||||
Pick<
|
||||
formatValueOptions,
|
||||
'maximumFractionDigits' | 'minimumFractionDigits'
|
||||
> & {
|
||||
nilValueSymbol: string
|
||||
children: number
|
||||
negative: boolean
|
||||
customCSS: string
|
||||
}
|
||||
>
|
||||
|
||||
export default function Value({
|
||||
nodeValue: value,
|
||||
unit,
|
||||
|
@ -25,7 +39,7 @@ export default function Value({
|
|||
children,
|
||||
negative,
|
||||
customCSS = ''
|
||||
}) {
|
||||
}: ValueProps) {
|
||||
const { language } = useTranslation().i18n
|
||||
|
||||
/* Either an entire rule object is passed, or just the right attributes and the value as a JSX child*/
|
||||
|
@ -46,7 +60,7 @@ export default function Value({
|
|||
valueType === 'string' ? (
|
||||
<T>{nodeValue}</T>
|
||||
) : valueType === 'object' ? (
|
||||
nodeValue.nom
|
||||
(nodeValue as any).nom
|
||||
) : valueType === 'boolean' ? (
|
||||
booleanTranslations[language][nodeValue]
|
||||
) : (
|
|
@ -1,64 +0,0 @@
|
|||
import { EXPLAIN_VARIABLE } from 'Actions/actions'
|
||||
import Animate from 'Components/ui/animate'
|
||||
import { Markdown } from 'Components/utils/markdown'
|
||||
import withColours from 'Components/utils/withColours'
|
||||
import { findRuleByDottedName } from 'Engine/rules'
|
||||
import { compose } from 'ramda'
|
||||
import React from 'react'
|
||||
import emoji from 'react-easy-emoji'
|
||||
import { connect } from 'react-redux'
|
||||
import { flatRulesSelector } from 'Selectors/analyseSelectors'
|
||||
import References from '../rule/References'
|
||||
import './Aide.css'
|
||||
|
||||
export default compose(
|
||||
connect(
|
||||
state => ({
|
||||
explained: state.explainedVariable,
|
||||
flatRules: flatRulesSelector(state)
|
||||
}),
|
||||
dispatch => ({
|
||||
stopExplaining: () => dispatch({ type: EXPLAIN_VARIABLE })
|
||||
})
|
||||
),
|
||||
withColours
|
||||
)(function Aide({ flatRules, explained, stopExplaining, colours }) {
|
||||
if (!explained) return <section id="helpWrapper" />
|
||||
|
||||
let rule = findRuleByDottedName(flatRules, explained),
|
||||
text = rule.description,
|
||||
refs = rule.références
|
||||
|
||||
return (
|
||||
<Animate.fromTop><div css={`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
img {
|
||||
margin: 0 1em 0 !important;
|
||||
width: 1.6em !important;
|
||||
height: 1.6em !important;
|
||||
}
|
||||
`}>
|
||||
{emoji('ℹ️')}
|
||||
|
||||
<div className="controlText ui__ card" css="padding: 0.6rem 0; flex: 1;">
|
||||
<h4>{rule.title}</h4>
|
||||
<p>
|
||||
<Markdown source={text} />
|
||||
</p>
|
||||
{refs && (
|
||||
<div>
|
||||
<p>Pour en savoir plus: </p>
|
||||
<References refs={refs} />
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
className="hide"
|
||||
aria-label="close"
|
||||
onClick={stopExplaining}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div></div></Animate.fromTop>
|
||||
)
|
||||
})
|
|
@ -0,0 +1,62 @@
|
|||
import { explainVariable } from 'Actions/actions'
|
||||
import Animate from 'Components/ui/animate'
|
||||
import { Markdown } from 'Components/utils/markdown'
|
||||
import { findRuleByDottedName } from 'Engine/rules'
|
||||
import React from 'react'
|
||||
import emoji from 'react-easy-emoji'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { RootState } from 'Reducers/rootReducer'
|
||||
import { flatRulesSelector } from 'Selectors/analyseSelectors'
|
||||
import References from '../rule/References'
|
||||
import './Aide.css'
|
||||
|
||||
export default function Aide() {
|
||||
const explained = useSelector((state: RootState) => state.explainedVariable)
|
||||
const flatRules = useSelector(flatRulesSelector)
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const stopExplaining = () => dispatch(explainVariable())
|
||||
|
||||
if (!explained) return <section id="helpWrapper" />
|
||||
|
||||
let rule = findRuleByDottedName(flatRules, explained),
|
||||
text = rule.description,
|
||||
refs = rule.références
|
||||
|
||||
return (
|
||||
<Animate.fromTop>
|
||||
<div
|
||||
css={`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
img {
|
||||
margin: 0 1em 0 !important;
|
||||
width: 1.6em !important;
|
||||
height: 1.6em !important;
|
||||
}
|
||||
`}
|
||||
>
|
||||
{emoji('ℹ️')}
|
||||
|
||||
<div
|
||||
className="controlText ui__ card"
|
||||
css="padding: 0.6rem 0; flex: 1;"
|
||||
>
|
||||
<h4>{rule.title}</h4>
|
||||
<p>
|
||||
<Markdown source={text} />
|
||||
</p>
|
||||
{refs && (
|
||||
<div>
|
||||
<p>Pour en savoir plus: </p>
|
||||
<References refs={refs} />
|
||||
</div>
|
||||
)}
|
||||
<button className="hide" aria-label="close" onClick={stopExplaining}>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Animate.fromTop>
|
||||
)
|
||||
}
|
|
@ -1,22 +1,31 @@
|
|||
import { goToQuestion, validateStepWithValue } from 'Actions/actions'
|
||||
import { T } from 'Components'
|
||||
import QuickLinks from 'Components/QuickLinks'
|
||||
import getInputComponent from 'Engine/getInputComponent'
|
||||
import getInputComponent from 'Engine/getInputComponent'
|
||||
import { findRuleByDottedName } from 'Engine/rules'
|
||||
import React from 'react'
|
||||
import emoji from 'react-easy-emoji'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { currentQuestionSelector, flatRulesSelector, nextStepsSelector } from 'Selectors/analyseSelectors'
|
||||
import { RootState } from 'Reducers/rootReducer'
|
||||
import {
|
||||
currentQuestionSelector,
|
||||
flatRulesSelector,
|
||||
nextStepsSelector
|
||||
} from 'Selectors/analyseSelectors'
|
||||
import * as Animate from 'Ui/animate'
|
||||
import Aide from './Aide'
|
||||
import './conversation.css'
|
||||
|
||||
export default function Conversation({ customEndMessages }) {
|
||||
export type ConversationProps = {
|
||||
customEndMessages?: React.ReactNode
|
||||
}
|
||||
|
||||
export default function Conversation({ customEndMessages }: ConversationProps) {
|
||||
const dispatch = useDispatch()
|
||||
const flatRules = useSelector(flatRulesSelector)
|
||||
const currentQuestion = useSelector(currentQuestionSelector)
|
||||
const previousAnswers = useSelector(
|
||||
state => state.conversationSteps.foldedSteps
|
||||
(state: RootState) => state.conversationSteps.foldedSteps
|
||||
)
|
||||
const nextSteps = useSelector(nextStepsSelector)
|
||||
|
||||
|
@ -29,7 +38,7 @@ export default function Conversation({ customEndMessages }) {
|
|||
)
|
||||
const goToPrevious = () =>
|
||||
dispatch(goToQuestion(previousAnswers.slice(-1)[0]))
|
||||
const handleKeyDown = ({ key }) => {
|
||||
const handleKeyDown = ({ key }: React.KeyboardEvent) => {
|
||||
if (['Escape'].includes(key)) {
|
||||
setDefault()
|
||||
}
|
||||
|
@ -38,7 +47,7 @@ export default function Conversation({ customEndMessages }) {
|
|||
return nextSteps.length ? (
|
||||
<>
|
||||
<Aide />
|
||||
<div tabIndex="0" style={{ outline: 'none' }} onKeyDown={handleKeyDown}>
|
||||
<div tabIndex={0} style={{ outline: 'none' }} onKeyDown={handleKeyDown}>
|
||||
{currentQuestion && (
|
||||
<React.Fragment key={currentQuestion}>
|
||||
<Animate.fadeIn>
|
||||
|
@ -49,14 +58,16 @@ export default function Conversation({ customEndMessages }) {
|
|||
<>
|
||||
<button
|
||||
onClick={goToPrevious}
|
||||
className="ui__ simple small push-left button">
|
||||
className="ui__ simple small push-left button"
|
||||
>
|
||||
← <T>Précédent</T>
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
onClick={setDefault}
|
||||
className="ui__ simple small push-right button">
|
||||
className="ui__ simple small push-right button"
|
||||
>
|
||||
<T>Passer</T> →
|
||||
</button>
|
||||
</div>
|
||||
|
@ -66,20 +77,20 @@ export default function Conversation({ customEndMessages }) {
|
|||
<QuickLinks />
|
||||
</>
|
||||
) : (
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<h3>
|
||||
{emoji('🌟')}{' '}
|
||||
<T k="simulation-end.title">Vous avez complété cette simulation</T>{' '}
|
||||
</h3>
|
||||
<p>
|
||||
{customEndMessages ? (
|
||||
customEndMessages
|
||||
) : (
|
||||
<T k="simulation-end.text">
|
||||
Vous avez maintenant accès à l'estimation la plus précise possible.
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<h3>
|
||||
{emoji('🌟')}{' '}
|
||||
<T k="simulation-end.title">Vous avez complété cette simulation</T>{' '}
|
||||
</h3>
|
||||
<p>
|
||||
{customEndMessages ? (
|
||||
customEndMessages
|
||||
) : (
|
||||
<T k="simulation-end.text">
|
||||
Vous avez maintenant accès à l'estimation la plus précise possible.
|
||||
</T>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,27 +1,21 @@
|
|||
import { EXPLAIN_VARIABLE } from 'Actions/actions'
|
||||
import { explainVariable } from 'Actions/actions'
|
||||
import classNames from 'classnames'
|
||||
import { findRuleByDottedName } from 'Engine/rules'
|
||||
import { compose } from 'ramda'
|
||||
import React from 'react'
|
||||
import React, { useContext } from 'react'
|
||||
import emoji from 'react-easy-emoji'
|
||||
import { connect } from 'react-redux'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { RootState } from 'Reducers/rootReducer'
|
||||
import { flatRulesSelector } from 'Selectors/analyseSelectors'
|
||||
import withTracker from '../utils/withTracker'
|
||||
import { DottedName } from 'Types/rule'
|
||||
import { TrackerContext } from '../utils/withTracker'
|
||||
import './Explicable.css'
|
||||
|
||||
export default compose(
|
||||
connect(
|
||||
state => ({
|
||||
explained: state.explainedVariable,
|
||||
flatRules: flatRulesSelector(state)
|
||||
}),
|
||||
dispatch => ({
|
||||
explain: variableName =>
|
||||
dispatch({ type: EXPLAIN_VARIABLE, variableName })
|
||||
})
|
||||
),
|
||||
withTracker
|
||||
)(function Explicable({ flatRules, dottedName, explain, explained, tracker }) {
|
||||
export default function Explicable({ dottedName }: { dottedName: DottedName }) {
|
||||
const tracker = useContext(TrackerContext)
|
||||
const dispatch = useDispatch()
|
||||
const explained = useSelector((state: RootState) => state.explainedVariable)
|
||||
const flatRules = useSelector(flatRulesSelector)
|
||||
|
||||
// Rien à expliquer ici, ce n'est pas une règle
|
||||
if (dottedName == null) return null
|
||||
|
||||
|
@ -35,17 +29,19 @@ export default compose(
|
|||
<span
|
||||
className={classNames('explicable', {
|
||||
explained: dottedName === explained
|
||||
})}>
|
||||
})}
|
||||
>
|
||||
<span
|
||||
className="icon"
|
||||
onClick={e => {
|
||||
tracker.push(['trackEvent', 'help', dottedName])
|
||||
explain(dottedName)
|
||||
dispatch(explainVariable(dottedName))
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
{emoji('ℹ️')}
|
||||
</span>
|
||||
</span>
|
||||
)
|
||||
})
|
||||
}
|
|
@ -1,23 +1,26 @@
|
|||
import { T } from 'Components'
|
||||
import React, { useState } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { RootState } from 'Reducers/rootReducer'
|
||||
import Answers from './AnswerList'
|
||||
import './conversation.css'
|
||||
|
||||
export default connect(state => ({
|
||||
arePreviousAnswers: !!state.conversationSteps.foldedSteps.length
|
||||
}))(function SeeAnswersButton({ arePreviousAnswers }) {
|
||||
export default function SeeAnswersButton() {
|
||||
const arePreviousAnswers = !!useSelector(
|
||||
(state: RootState) => state.conversationSteps.foldedSteps.length
|
||||
)
|
||||
const [showAnswerModal, setShowAnswerModal] = useState(false)
|
||||
return (
|
||||
<>
|
||||
{arePreviousAnswers && (
|
||||
<button
|
||||
className="ui__ small simple button "
|
||||
onClick={() => setShowAnswerModal(true)}>
|
||||
onClick={() => setShowAnswerModal(true)}
|
||||
>
|
||||
<T>Modifier mes réponses</T>
|
||||
</button>
|
||||
)}
|
||||
{showAnswerModal && <Answers onClose={() => setShowAnswerModal(false)} />}
|
||||
</>
|
||||
)
|
||||
})
|
||||
}
|
|
@ -1,13 +1,18 @@
|
|||
import React, { useCallback, useEffect } from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
|
||||
export default function SendButton({ disabled, submit }) {
|
||||
type SendButtonProps = {
|
||||
disabled: boolean
|
||||
submit: (cause: string) => void
|
||||
}
|
||||
|
||||
export default function SendButton({ disabled, submit }: SendButtonProps) {
|
||||
const getAction = useCallback(cause => (!disabled ? submit(cause) : null), [
|
||||
disabled,
|
||||
submit
|
||||
])
|
||||
useEffect(() => {
|
||||
const handleKeyDown = ({ key }) => {
|
||||
const handleKeyDown = ({ key }: KeyboardEvent) => {
|
||||
if (key !== 'Enter') return
|
||||
getAction('enter')
|
||||
}
|
||||
|
@ -23,7 +28,8 @@ export default function SendButton({ disabled, submit }) {
|
|||
className="ui__ button plain"
|
||||
css="margin-left: 1.2rem"
|
||||
disabled={disabled}
|
||||
onClick={() => getAction('accept')}>
|
||||
onClick={() => getAction('accept')}
|
||||
>
|
||||
<span className="text">
|
||||
<Trans>Suivant</Trans> →
|
||||
</span>
|
|
@ -1,6 +0,0 @@
|
|||
import React from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
|
||||
let T = ({ k, ...props }) => <Trans i18nKey={k} {...props} />
|
||||
|
||||
export { T }
|
|
@ -0,0 +1,8 @@
|
|||
import React from 'react'
|
||||
import { Trans, TransProps } from 'react-i18next'
|
||||
|
||||
type TProps = { k?: TransProps['i18nKey'] } & TransProps
|
||||
|
||||
const T = ({ k, ...props }: TProps) => <Trans i18nKey={k} {...props} />
|
||||
|
||||
export { T }
|
|
@ -1,12 +1,13 @@
|
|||
import withSitePaths from 'Components/utils/withSitePaths'
|
||||
import { SitePathsContext } from 'Components/utils/withSitePaths'
|
||||
import { encodeRuleName, findRuleByDottedName } from 'Engine/rules'
|
||||
import React from 'react'
|
||||
import React, { useContext } from 'react'
|
||||
import emoji from 'react-easy-emoji'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { capitalise0 } from '../../utils'
|
||||
import './Namespace.css'
|
||||
|
||||
let Namespace = ({ dottedName, flatRules, colour, sitePaths }) => {
|
||||
export default function Namespace({ dottedName, flatRules, colour }) {
|
||||
const sitePaths = useContext(SitePathsContext)
|
||||
return (
|
||||
<ul id="namespace">
|
||||
{dottedName
|
||||
|
@ -36,7 +37,8 @@ let Namespace = ({ dottedName, flatRules, colour, sitePaths }) => {
|
|||
style={style}
|
||||
to={
|
||||
sitePaths.documentation.index + '/' + encodeRuleName(ruleName)
|
||||
}>
|
||||
}
|
||||
>
|
||||
{rule.icons && <span>{emoji(rule.icons)} </span>}
|
||||
{ruleText}
|
||||
</Link>
|
||||
|
@ -47,4 +49,3 @@ let Namespace = ({ dottedName, flatRules, colour, sitePaths }) => {
|
|||
</ul>
|
||||
)
|
||||
}
|
||||
export default withSitePaths(Namespace)
|
||||
|
|
|
@ -32,8 +32,8 @@ function Ref({ name, link }) {
|
|||
)
|
||||
}
|
||||
|
||||
interface ReferencesProps {
|
||||
refs: { [key: string]: string }
|
||||
type ReferencesProps = {
|
||||
refs: { [name: string]: string }
|
||||
}
|
||||
|
||||
export default function References({ refs }: ReferencesProps) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { T } from 'Components'
|
||||
import PeriodSwitch from 'Components/PeriodSwitch'
|
||||
import withColours from 'Components/utils/withColours'
|
||||
import withSitePaths from 'Components/utils/withSitePaths'
|
||||
import { SitePathsContext } from 'Components/utils/withSitePaths'
|
||||
import Value from 'Components/Value'
|
||||
import knownMecanisms from 'Engine/known-mecanisms.yaml'
|
||||
import {
|
||||
|
@ -10,7 +10,7 @@ import {
|
|||
findRuleByNamespace
|
||||
} from 'Engine/rules'
|
||||
import { compose, isEmpty } from 'ramda'
|
||||
import React, { Suspense, useState } from 'react'
|
||||
import React, { Suspense, useContext, useState } from 'react'
|
||||
import emoji from 'react-easy-emoji'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
@ -42,17 +42,16 @@ export default compose(
|
|||
analysedRule: ruleAnalysisSelector(state, props),
|
||||
analysedExample: exampleAnalysisSelector(state, props)
|
||||
})),
|
||||
AttachDictionary(knownMecanisms),
|
||||
withSitePaths
|
||||
AttachDictionary(knownMecanisms)
|
||||
)(function Rule({
|
||||
dottedName,
|
||||
currentExample,
|
||||
flatRules,
|
||||
valuesToShow,
|
||||
sitePaths,
|
||||
analysedExample,
|
||||
analysedRule
|
||||
}) {
|
||||
const sitePaths = useContext(SitePathsContext)
|
||||
const [viewSource, setViewSource] = useState(false)
|
||||
const { t } = useTranslation()
|
||||
|
||||
|
@ -60,13 +59,14 @@ export default compose(
|
|||
let { type, name, acronyme, title, description, question, icon } = flatRule,
|
||||
namespaceRules = findRuleByNamespace(flatRules, dottedName)
|
||||
let displayedRule = analysedExample || analysedRule
|
||||
|
||||
|
||||
const renderToggleSourceButton = () => {
|
||||
return (
|
||||
<button
|
||||
id="toggleRuleSource"
|
||||
className="ui__ link-button"
|
||||
onClick={() => setViewSource(!viewSource)}>
|
||||
onClick={() => setViewSource(!viewSource)}
|
||||
>
|
||||
{emoji(
|
||||
viewSource
|
||||
? `📖 ${t('Revenir à la documentation')}`
|
||||
|
@ -140,7 +140,8 @@ export default compose(
|
|||
> * {
|
||||
margin: 0 0.6em;
|
||||
}
|
||||
`}>
|
||||
`}
|
||||
>
|
||||
<Value
|
||||
{...displayedRule}
|
||||
nilValueSymbol={
|
||||
|
@ -177,7 +178,8 @@ export default compose(
|
|||
? sitePaths.simulateurs.indépendant
|
||||
: // otherwise
|
||||
sitePaths.simulateurs.index
|
||||
}>
|
||||
}
|
||||
>
|
||||
<T>Faire une simulation</T>
|
||||
</Link>
|
||||
</div>
|
||||
|
@ -224,10 +226,8 @@ export default compose(
|
|||
)
|
||||
})
|
||||
|
||||
let NamespaceRulesList = compose(
|
||||
withColours,
|
||||
withSitePaths
|
||||
)(({ namespaceRules, colours, sitePaths }) => {
|
||||
let NamespaceRulesList = compose(withColours)(({ namespaceRules, colours }) => {
|
||||
const sitePaths = useContext(SitePathsContext)
|
||||
return (
|
||||
<section>
|
||||
<h2>
|
||||
|
@ -245,7 +245,8 @@ let NamespaceRulesList = compose(
|
|||
sitePaths.documentation.index +
|
||||
'/' +
|
||||
encodeRuleName(r.dottedName)
|
||||
}>
|
||||
}
|
||||
>
|
||||
{r.title || r.name}
|
||||
</Link>
|
||||
</li>
|
||||
|
@ -264,7 +265,8 @@ let Period = ({ period, valuesToShow }) =>
|
|||
<span
|
||||
className="name"
|
||||
data-term-definition="période"
|
||||
style={{ background: '#8e44ad' }}>
|
||||
style={{ background: '#8e44ad' }}
|
||||
>
|
||||
{period}
|
||||
</span>
|
||||
</span>
|
||||
|
|
|
@ -2,9 +2,12 @@ import { safeDump } from 'js-yaml'
|
|||
import React from 'react'
|
||||
import emoji from 'react-easy-emoji'
|
||||
import rules from 'Règles/base.yaml'
|
||||
import { Rule } from 'Types/rule'
|
||||
import ColoredYaml from './ColoredYaml'
|
||||
|
||||
export default function RuleSource({ dottedName }) {
|
||||
type RuleSourceProps = Pick<Rule, 'dottedName'>
|
||||
|
||||
export default function RuleSource({ dottedName }: RuleSourceProps) {
|
||||
let source = rules[dottedName]
|
||||
|
||||
return (
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
import { resetSimulation, setSimulationConfig } from 'Actions/actions'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { RootState, SimulationConfig } from 'Reducers/rootReducer'
|
||||
|
||||
export function useSimulationConfig(config: SimulationConfig) {
|
||||
const dispatch = useDispatch()
|
||||
const stateConfig = useSelector(
|
||||
(state: RootState) => state.simulation?.config
|
||||
)
|
||||
if (config !== stateConfig) {
|
||||
dispatch(setSimulationConfig(config))
|
||||
if (stateConfig) {
|
||||
dispatch(resetSimulation())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
import { resetSimulation, setSimulationConfig } from 'Actions/actions'
|
||||
import { compose } from 'ramda'
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { noUserInputSelector } from 'Selectors/analyseSelectors'
|
||||
|
||||
export default config => SimulationComponent =>
|
||||
compose(
|
||||
connect(
|
||||
state => ({
|
||||
config: state.simulation?.config,
|
||||
noUserInput: noUserInputSelector(state)
|
||||
}),
|
||||
{
|
||||
setSimulationConfig,
|
||||
resetSimulation
|
||||
}
|
||||
)
|
||||
)(function DecoratedSimulation(props) {
|
||||
if (config !== props.config) {
|
||||
props.setSimulationConfig(config)
|
||||
if (props.config) {
|
||||
props.resetSimulation()
|
||||
}
|
||||
}
|
||||
if (!config) return null
|
||||
return <SimulationComponent {...props} />
|
||||
})
|
|
@ -5,12 +5,12 @@ import { useTranslation } from 'react-i18next'
|
|||
import { usePeriod } from 'Selectors/analyseSelectors'
|
||||
import './AnimatedTargetValue.css'
|
||||
|
||||
interface AnimatedTargetValueProps {
|
||||
type AnimatedTargetValueProps = {
|
||||
value?: number
|
||||
children: React.ReactNode
|
||||
children?: React.ReactNode
|
||||
}
|
||||
|
||||
function formatDifference(difference, language) {
|
||||
const formatDifference: typeof formatCurrency = (difference, language) => {
|
||||
const prefix = difference > 0 ? '+' : ''
|
||||
return prefix + formatCurrency(difference, language)
|
||||
}
|
||||
|
@ -46,7 +46,8 @@ export default function AnimatedTargetValue({
|
|||
style={{
|
||||
color: difference > 0 ? 'chartreuse' : 'red',
|
||||
pointerEvents: 'none'
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
{formatDifference(difference, language)}
|
||||
</Evaporate>
|
||||
)}{' '}
|
||||
|
@ -61,7 +62,8 @@ const Evaporate = React.memo(
|
|||
<ReactCSSTransitionGroup
|
||||
transitionName="evaporate"
|
||||
transitionEnterTimeout={2500}
|
||||
transitionLeaveTimeout={1}>
|
||||
transitionLeaveTimeout={1}
|
||||
>
|
||||
<span key={children} style={style} className="evaporate">
|
||||
{children}
|
||||
</span>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import classnames from 'classnames'
|
||||
import Animate from 'Ui/animate'
|
||||
import { Markdown } from 'Components/utils/markdown'
|
||||
import { ScrollToElement } from 'Components/utils/Scroll'
|
||||
import { TrackerContext } from 'Components/utils/withTracker'
|
||||
import React, { Component, useContext, useState } from 'react'
|
||||
import Animate from 'Ui/animate'
|
||||
import Checkbox from '../Checkbox'
|
||||
import './index.css'
|
||||
|
||||
|
@ -58,7 +58,8 @@ export function CheckItem({
|
|||
className={classnames('ui__ checklist-button', {
|
||||
opened: displayExplanations
|
||||
})}
|
||||
onClick={handleClick}>
|
||||
onClick={handleClick}
|
||||
>
|
||||
{title}
|
||||
</button>
|
||||
</div>
|
||||
|
@ -78,7 +79,7 @@ export function CheckItem({
|
|||
)
|
||||
}
|
||||
|
||||
type ChecklistProps = {
|
||||
export type ChecklistProps = {
|
||||
children: React.ReactNode
|
||||
onItemCheck: (string, boolean) => void
|
||||
onInitialization: (arg: Array<string>) => void
|
||||
|
|
|
@ -1,7 +1,17 @@
|
|||
import React from 'react'
|
||||
import './Progress.css'
|
||||
|
||||
export default function Progress({ progress, style, className }) {
|
||||
type ProgressProps = {
|
||||
progress: number
|
||||
style?: React.CSSProperties
|
||||
className: string
|
||||
}
|
||||
|
||||
export default function Progress({
|
||||
progress,
|
||||
style,
|
||||
className
|
||||
}: ProgressProps) {
|
||||
return (
|
||||
<div className={'progress__container ' + className} style={style}>
|
||||
<div className="progress__bar" style={{ width: `${progress * 100}%` }} />
|
||||
|
|
|
@ -1,13 +1,5 @@
|
|||
import withColours, { ThemeColours } from 'Components/utils/withColours'
|
||||
import React from 'react'
|
||||
|
||||
type OwnProps = {
|
||||
media: 'email' | 'facebook' | 'linkedin' | 'github' | 'twitter'
|
||||
}
|
||||
|
||||
type Props = {
|
||||
colours: ThemeColours
|
||||
} & OwnProps
|
||||
import { ThemeColoursContext } from 'Components/utils/withColours'
|
||||
import React, { useContext } from 'react'
|
||||
|
||||
const icons = {
|
||||
facebook: {
|
||||
|
@ -42,10 +34,12 @@ const icons = {
|
|||
}
|
||||
}
|
||||
|
||||
export default withColours(function withSocialMedia({
|
||||
media,
|
||||
colours: { colour }
|
||||
}: Props) {
|
||||
export default function withSocialMedia({
|
||||
media
|
||||
}: {
|
||||
media: keyof typeof icons
|
||||
}) {
|
||||
const { colour } = useContext(ThemeColoursContext)
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 64 64"
|
||||
|
@ -55,7 +49,8 @@ export default withColours(function withSocialMedia({
|
|||
height: '2rem',
|
||||
margin: '0.6rem',
|
||||
fillRule: 'evenodd'
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<g
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
|
@ -64,7 +59,8 @@ export default withColours(function withSocialMedia({
|
|||
position: 'relative',
|
||||
overflow: 'hidden',
|
||||
verticalAlign: 'middle'
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<circle cx="32" cy="32" r="31" />
|
||||
</g>
|
||||
<g>
|
||||
|
@ -75,4 +71,4 @@ export default withColours(function withSocialMedia({
|
|||
</g>
|
||||
</svg>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -14,18 +14,16 @@ function LinkRenderer({ href, children }) {
|
|||
}
|
||||
}
|
||||
|
||||
interface MarkdownProps {
|
||||
type MarkdownProps = ReactMarkdownProps & {
|
||||
source: string
|
||||
className?: string
|
||||
renderers?: ReactMarkdownProps['renderers']
|
||||
[other_props: string]: any
|
||||
}
|
||||
|
||||
export const Markdown = ({
|
||||
source,
|
||||
className = '',
|
||||
renderers = {},
|
||||
otherProps
|
||||
...otherProps
|
||||
}: MarkdownProps) => (
|
||||
<ReactMarkdown
|
||||
source={source}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import safeLocalStorage from '../../storage/safeLocalStorage'
|
||||
|
||||
export const persistState = key => ([state, changeState]) => {
|
||||
export const persistState = (key: string) => ([state, changeState]) => {
|
||||
useEffect(() => safeLocalStorage.setItem(key, JSON.stringify(state)), [state])
|
||||
return [state, changeState]
|
||||
}
|
||||
export const getInitialState = key => {
|
||||
|
||||
export const getInitialState = (key: string) => {
|
||||
const value = safeLocalStorage.getItem(key)
|
||||
if (!value) {
|
||||
return
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import { useEffect, useRef, useState } from 'react'
|
||||
|
||||
export default function({ root = null, rootMargin, threshold = 0 }) {
|
||||
const ref = useRef()
|
||||
export default function({
|
||||
root = null,
|
||||
rootMargin,
|
||||
threshold = 0
|
||||
}: IntersectionObserverInit): [React.RefObject<HTMLDivElement>, boolean] {
|
||||
const ref = useRef<HTMLDivElement>()
|
||||
const [wasOnScreen, setWasOnScreen] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -25,7 +29,7 @@ export default function({ root = null, rootMargin, threshold = 0 }) {
|
|||
return () => {
|
||||
observer.unobserve(node)
|
||||
}
|
||||
}, [root, rootMargin, threshold]) // Empty array ensures that effect is only run on mount and unmount
|
||||
}, [root, rootMargin, threshold])
|
||||
|
||||
return [ref, wasOnScreen]
|
||||
}
|
|
@ -111,7 +111,7 @@ export function ThemeColoursProvider({ colour, children }: ProviderProps) {
|
|||
)
|
||||
}
|
||||
|
||||
interface WithColoursProps {
|
||||
type WithColoursProps = {
|
||||
colours: ThemeColours
|
||||
}
|
||||
|
||||
|
|
|
@ -1,30 +1,10 @@
|
|||
import React, { createContext } from 'react'
|
||||
import { createContext } from 'react'
|
||||
import { SitePathsType } from 'sites/mon-entreprise.fr/sitePaths'
|
||||
|
||||
export const SitePathsContext = createContext<Partial<SitePathsType>>({})
|
||||
export const SitePathsContext = createContext<SitePathsType>(
|
||||
{} as SitePathsType
|
||||
)
|
||||
|
||||
export const SitePathProvider = SitePathsContext.Provider
|
||||
|
||||
export interface WithSitePathsProps {
|
||||
sitePaths: SitePathsType
|
||||
}
|
||||
|
||||
export default function withSitePaths<P extends object>(
|
||||
WrappedComponent: React.ComponentType<P>
|
||||
) {
|
||||
class WithSitePaths extends React.Component<P & WithSitePathsProps> {
|
||||
displayName = `withSitePaths(${WrappedComponent.displayName || ''})`
|
||||
render() {
|
||||
return (
|
||||
<SitePathsContext.Consumer>
|
||||
{sitePaths => (
|
||||
<WrappedComponent {...(this.props as P)} sitePaths={sitePaths} />
|
||||
)}
|
||||
</SitePathsContext.Consumer>
|
||||
)
|
||||
}
|
||||
}
|
||||
return WithSitePaths
|
||||
}
|
||||
|
||||
export type SitePaths = SitePathsType
|
||||
|
|
|
@ -1,19 +1,5 @@
|
|||
import React, { createContext } from 'react'
|
||||
import { createContext } from 'react'
|
||||
import Tracker, { devTracker } from '../../Tracker'
|
||||
|
||||
export const TrackerContext = createContext(devTracker)
|
||||
export const TrackerContext = createContext<Tracker>(devTracker)
|
||||
export const TrackerProvider = TrackerContext.Provider
|
||||
|
||||
export interface WithTrackerProps {
|
||||
tracker: Tracker
|
||||
}
|
||||
|
||||
export default function withTracker(Component: React.ComponentType) {
|
||||
return function ConnectTracker(props) {
|
||||
return (
|
||||
<TrackerContext.Consumer>
|
||||
{(tracker: Tracker) => <Component {...props} tracker={tracker} />}
|
||||
</TrackerContext.Consumer>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { serialiseUnit } from 'Engine/units'
|
||||
import { memoizeWith } from 'ramda'
|
||||
import { Unit } from './units'
|
||||
|
||||
const NumberFormat = memoizeWith(
|
||||
(...args) => JSON.stringify(args),
|
||||
|
@ -11,6 +12,11 @@ export let numberFormatter = ({
|
|||
maximumFractionDigits = 2,
|
||||
minimumFractionDigits = 0,
|
||||
language
|
||||
}: {
|
||||
style?: string
|
||||
maximumFractionDigits?: number
|
||||
minimumFractionDigits?: number
|
||||
language?: string
|
||||
}) => value => {
|
||||
// When we format currency we don't want to display a single decimal digit
|
||||
// ie 8,1€ but we want to display 8,10€
|
||||
|
@ -29,7 +35,7 @@ export let numberFormatter = ({
|
|||
}).format(value)
|
||||
}
|
||||
|
||||
export const currencyFormat = language => ({
|
||||
export const currencyFormat = (language: string) => ({
|
||||
isCurrencyPrefixed: !!numberFormatter({ language, style: 'currency' })(
|
||||
12
|
||||
).match(/^€/),
|
||||
|
@ -37,7 +43,7 @@ export const currencyFormat = language => ({
|
|||
decimalSeparator: numberFormatter({ language })(0.1).charAt(1)
|
||||
})
|
||||
|
||||
export const formatCurrency = (value, language) => {
|
||||
export const formatCurrency = (value: number, language: string) => {
|
||||
return value == null
|
||||
? ''
|
||||
: formatValue({ unit: '€', language, value }).replace(/^(-)?€/, '$1€\u00A0')
|
||||
|
@ -48,13 +54,21 @@ export const formatPercentage = value =>
|
|||
? ''
|
||||
: formatValue({ unit: '%', value, maximumFractionDigits: 2 })
|
||||
|
||||
export type formatValueOptions = {
|
||||
maximumFractionDigits?: number
|
||||
minimumFractionDigits?: number
|
||||
language?: string
|
||||
unit: Unit | string
|
||||
value: number
|
||||
}
|
||||
|
||||
export function formatValue({
|
||||
maximumFractionDigits,
|
||||
minimumFractionDigits,
|
||||
language,
|
||||
unit,
|
||||
value
|
||||
}) {
|
||||
}: formatValueOptions) {
|
||||
if (typeof value !== 'number') {
|
||||
return value
|
||||
}
|
||||
|
@ -69,7 +83,11 @@ export function formatValue({
|
|||
language
|
||||
})(value)
|
||||
case '%':
|
||||
return numberFormatter({ style: 'percent', maximumFractionDigits })(value)
|
||||
return numberFormatter({
|
||||
style: 'percent',
|
||||
maximumFractionDigits,
|
||||
language
|
||||
})(value)
|
||||
default:
|
||||
return (
|
||||
numberFormatter({
|
|
@ -1,10 +1,10 @@
|
|||
import { default as classNames, default as classnames } from 'classnames'
|
||||
import withSitePaths from 'Components/utils/withSitePaths'
|
||||
import { SitePathsContext } from 'Components/utils/withSitePaths'
|
||||
import Value from 'Components/Value'
|
||||
import { compose, contains, isNil, pipe, sort, toPairs } from 'ramda'
|
||||
import React from 'react'
|
||||
import { contains, isNil, pipe, sort, toPairs } from 'ramda'
|
||||
import React, { useContext } from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { connect } from 'react-redux'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { flatRulesSelector } from 'Selectors/analyseSelectors'
|
||||
import { LinkButton } from 'Ui/Button'
|
||||
|
@ -26,7 +26,8 @@ export let NodeValuePointer = ({ data, unit }) => (
|
|||
box-shadow: 2px 2px 4px 1px #d9d9d9, 0 0 0 1px #d9d9d9;
|
||||
line-height: 1.6em;
|
||||
border-radius: 0.2rem;
|
||||
`}>
|
||||
`}
|
||||
>
|
||||
<Value nodeValue={data} unit={unit} />
|
||||
</span>
|
||||
)
|
||||
|
@ -38,13 +39,15 @@ export function Node({ classes, name, value, child, inline, unit }) {
|
|||
return (
|
||||
<div
|
||||
className={classNames(classes, 'node', { inline })}
|
||||
style={termDefinition ? { borderColor: mecanismColours(name) } : {}}>
|
||||
style={termDefinition ? { borderColor: mecanismColours(name) } : {}}
|
||||
>
|
||||
{name && !inline && (
|
||||
<div className="nodeHead" css="margin-bottom: 1em">
|
||||
<LinkButton
|
||||
className="name"
|
||||
style={termDefinition ? { background: mecanismColours(name) } : {}}
|
||||
data-term-definition={termDefinition}>
|
||||
data-term-definition={termDefinition}
|
||||
>
|
||||
<Trans>{name}</Trans>
|
||||
</LinkButton>
|
||||
</div>
|
||||
|
@ -64,7 +67,8 @@ export function Node({ classes, name, value, child, inline, unit }) {
|
|||
width: 100%;
|
||||
text-align: right;
|
||||
}
|
||||
`}>
|
||||
`}
|
||||
>
|
||||
{value !== true && value !== false && !isNil(value) && (
|
||||
<span className="operator"> = </span>
|
||||
)}
|
||||
|
@ -81,7 +85,8 @@ export function InlineMecanism({ name }) {
|
|||
<LinkButton
|
||||
className="name"
|
||||
data-term-definition={name}
|
||||
style={{ background: mecanismColours(name) }}>
|
||||
style={{ background: mecanismColours(name) }}
|
||||
>
|
||||
<Trans>{name}</Trans>
|
||||
</LinkButton>
|
||||
</span>
|
||||
|
@ -89,19 +94,9 @@ export function InlineMecanism({ name }) {
|
|||
}
|
||||
|
||||
// Un élément du graphe de calcul qui a une valeur interprétée (à afficher)
|
||||
export const Leaf = compose(
|
||||
withSitePaths,
|
||||
connect(state => ({ flatRules: flatRulesSelector(state) }))
|
||||
)(function Leaf({
|
||||
classes,
|
||||
dottedName,
|
||||
name,
|
||||
nodeValue,
|
||||
flatRules,
|
||||
filter,
|
||||
sitePaths,
|
||||
unit
|
||||
}) {
|
||||
export function Leaf({ classes, dottedName, name, nodeValue, filter, unit }) {
|
||||
const sitePaths = useContext(SitePathsContext)
|
||||
const flatRules = useSelector(flatRulesSelector)
|
||||
let rule = findRuleByDottedName(flatRules, dottedName)
|
||||
|
||||
return (
|
||||
|
@ -111,7 +106,8 @@ export const Leaf = compose(
|
|||
<Link
|
||||
to={
|
||||
sitePaths.documentation.index + '/' + encodeRuleName(dottedName)
|
||||
}>
|
||||
}
|
||||
>
|
||||
<span className="name">
|
||||
{rule.title || capitalise0(name)} {filter}
|
||||
</span>
|
||||
|
@ -120,7 +116,8 @@ export const Leaf = compose(
|
|||
<span
|
||||
css={`
|
||||
margin: 0 0.3rem;
|
||||
`}>
|
||||
`}
|
||||
>
|
||||
<NodeValuePointer data={nodeValue} unit={unit} />
|
||||
</span>
|
||||
)}
|
||||
|
@ -128,7 +125,7 @@ export const Leaf = compose(
|
|||
)}
|
||||
</span>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export function SimpleRuleLink({ rule: { dottedName, title, name } }) {
|
||||
return (
|
||||
|
@ -139,7 +136,7 @@ export function SimpleRuleLink({ rule: { dottedName, title, name } }) {
|
|||
}
|
||||
|
||||
export let sortObjectByKeys = pipe(
|
||||
toPairs,
|
||||
toPairs as any,
|
||||
// we don't rely on the sorting of objects
|
||||
sort(([k1], [k2]) => k1 - k2)
|
||||
sort(([k1]: [number], [k2]: [number]) => k1 - k2)
|
||||
)
|
|
@ -1,8 +1,15 @@
|
|||
import { remove, isEmpty, unnest } from 'ramda'
|
||||
import { isEmpty, remove, unnest } from 'ramda'
|
||||
import i18n from '../i18n'
|
||||
|
||||
type BaseUnit = string
|
||||
|
||||
export type Unit = {
|
||||
numerators: Array<BaseUnit>
|
||||
denominators: Array<BaseUnit>
|
||||
}
|
||||
|
||||
//TODO this function does not handle complex units like passenger-kilometer/flight
|
||||
export let parseUnit = string => {
|
||||
export let parseUnit = (string: string): Unit => {
|
||||
let [a, b = ''] = string.split('/'),
|
||||
result = {
|
||||
numerators: a !== '' ? [getUnitKey(a)] : [],
|
||||
|
@ -14,21 +21,25 @@ export let parseUnit = string => {
|
|||
const translations = Object.entries(
|
||||
i18n.getResourceBundle(i18n.language, 'units')
|
||||
)
|
||||
function getUnitKey(unit) {
|
||||
function getUnitKey(unit: string): string {
|
||||
const key = translations
|
||||
.find(([, trans]) => trans === unit)?.[0]
|
||||
.replace(/_plural$/, '')
|
||||
return key || unit
|
||||
}
|
||||
|
||||
let printUnits = (units, count) =>
|
||||
let printUnits = (units: Array<string>, count: number): string =>
|
||||
units
|
||||
.filter(unit => unit !== '%')
|
||||
.map(unit => i18n.t(`units:${unit}`, { count }))
|
||||
.join('-')
|
||||
|
||||
const plural = 2
|
||||
export let serialiseUnit = (rawUnit, count = plural, lng = undefined) => {
|
||||
export let serialiseUnit = (
|
||||
rawUnit: Unit | null | string,
|
||||
count: number = plural,
|
||||
lng?: string
|
||||
) => {
|
||||
if (rawUnit === null || typeof rawUnit !== 'object') {
|
||||
return typeof rawUnit === 'string'
|
||||
? i18n.t(`units:${rawUnit}`, { count, lng })
|
||||
|
@ -54,8 +65,13 @@ export let serialiseUnit = (rawUnit, count = plural, lng = undefined) => {
|
|||
return string
|
||||
}
|
||||
|
||||
type SupportedOperators = '*' | '/' | '+' | '-'
|
||||
|
||||
let noUnit = { numerators: [], denominators: [] }
|
||||
export let inferUnit = (operator, rawUnits) => {
|
||||
export let inferUnit = (
|
||||
operator: SupportedOperators,
|
||||
rawUnits: Array<Unit>
|
||||
): Unit => {
|
||||
let units = rawUnits.map(u => u || noUnit)
|
||||
if (operator === '*')
|
||||
return simplify({
|
||||
|
@ -80,13 +96,14 @@ export let inferUnit = (operator, rawUnits) => {
|
|||
|
||||
return null
|
||||
}
|
||||
export let removeOnce = element => list => {
|
||||
|
||||
export let removeOnce = <T>(element: T) => (list: Array<T>): Array<T> => {
|
||||
let index = list.indexOf(element)
|
||||
if (index > -1) return remove(index, 1)(list)
|
||||
if (index > -1) return remove<T>(index, 1)(list)
|
||||
else return list
|
||||
}
|
||||
|
||||
let simplify = unit =>
|
||||
let simplify = (unit: Unit): Unit =>
|
||||
[...unit.numerators, ...unit.denominators].reduce(
|
||||
({ numerators, denominators }, next) =>
|
||||
numerators.includes(next) && denominators.includes(next)
|
|
@ -4,6 +4,8 @@ import enTranslations from './locales/en.yaml'
|
|||
import unitsTranslations from './locales/units.yaml'
|
||||
import { getSessionStorage } from './utils'
|
||||
|
||||
export type AvailableLangs = 'fr' | 'en'
|
||||
|
||||
let lang =
|
||||
(typeof document !== 'undefined' &&
|
||||
new URLSearchParams(document.location.search.substring(1)).get('lang')) ||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
|
||||
import { Action as CreationChecklistAction } from 'Actions/companyCreationChecklistActions'
|
||||
import { Action as HiringChecklist } from 'Actions/hiringChecklistAction'
|
||||
import { omit } from 'ramda'
|
||||
import { combineReducers } from 'redux'
|
||||
import type {
|
||||
import { LegalStatus } from 'Selectors/companyStatusSelectors'
|
||||
import {
|
||||
Action as CompanyStatusAction,
|
||||
LegalStatusRequirements,
|
||||
State
|
||||
LegalStatusRequirements
|
||||
} from 'Types/companyTypes'
|
||||
import type { Action as CreationChecklistAction } from 'Types/companyCreationChecklistTypes'
|
||||
import type { Action as HiringChecklist } from 'Types/hiringChecklistTypes'
|
||||
|
||||
type Action = CompanyStatusAction | CreationChecklistAction | HiringChecklist
|
||||
|
||||
function companyLegalStatus(
|
||||
|
@ -32,7 +32,10 @@ function companyLegalStatus(
|
|||
return state
|
||||
}
|
||||
|
||||
function hiringChecklist(state: { [string]: boolean } = {}, action: Action) {
|
||||
function hiringChecklist(
|
||||
state: { [key: string]: boolean } = {},
|
||||
action: Action
|
||||
) {
|
||||
switch (action.type) {
|
||||
case 'CHECK_HIRING_ITEM':
|
||||
return {
|
||||
|
@ -43,16 +46,16 @@ function hiringChecklist(state: { [string]: boolean } = {}, action: Action) {
|
|||
return Object.keys(state).length
|
||||
? state
|
||||
: action.checklistItems.reduce(
|
||||
(checklist, item) => ({ ...checklist, [item]: false }),
|
||||
{}
|
||||
)
|
||||
(checklist, item) => ({ ...checklist, [item]: false }),
|
||||
{}
|
||||
)
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
function companyCreationChecklist(
|
||||
state: { [string]: boolean } = {},
|
||||
state: { [key: string]: boolean } = {},
|
||||
action: Action
|
||||
) {
|
||||
switch (action.type) {
|
||||
|
@ -65,9 +68,9 @@ function companyCreationChecklist(
|
|||
return Object.keys(state).length
|
||||
? state
|
||||
: action.checklistItems.reduce(
|
||||
(checklist, item) => ({ ...checklist, [item]: false }),
|
||||
{}
|
||||
)
|
||||
(checklist, item) => ({ ...checklist, [item]: false }),
|
||||
{}
|
||||
)
|
||||
case 'RESET_COMPANY_STATUS_CHOICE':
|
||||
return {}
|
||||
default:
|
||||
|
@ -75,7 +78,10 @@ function companyCreationChecklist(
|
|||
}
|
||||
}
|
||||
|
||||
function companyStatusChoice(state: ?string = null, action: Action) {
|
||||
function companyStatusChoice(
|
||||
state: LegalStatus = null,
|
||||
action: Action
|
||||
): LegalStatus {
|
||||
if (action.type === 'RESET_COMPANY_STATUS_CHOICE') {
|
||||
return null
|
||||
}
|
||||
|
@ -85,7 +91,9 @@ function companyStatusChoice(state: ?string = null, action: Action) {
|
|||
return action.statusName
|
||||
}
|
||||
|
||||
const infereLegalStatusFromCategorieJuridique = catégorieJuridique => {
|
||||
const infereLegalStatusFromCategorieJuridique = (
|
||||
catégorieJuridique: string
|
||||
) => {
|
||||
/*
|
||||
Nous utilisons le code entreprise pour connaitre le statut juridique
|
||||
(voir https://www.insee.fr/fr/information/2028129)
|
||||
|
@ -114,14 +122,16 @@ const infereLegalStatusFromCategorieJuridique = catégorieJuridique => {
|
|||
}
|
||||
return 'NON_IMPLÉMENTÉ'
|
||||
}
|
||||
function existingCompany(
|
||||
state: ?{
|
||||
siren: string,
|
||||
catégorieJuridique: ?string,
|
||||
statutJuridique: string
|
||||
} = null,
|
||||
action
|
||||
) {
|
||||
|
||||
export type Company = {
|
||||
siren: string
|
||||
catégorieJuridique?: string
|
||||
statutJuridique?: string
|
||||
isAutoEntrepreneur?: boolean
|
||||
isDirigeantMajoritaire?: boolean
|
||||
}
|
||||
|
||||
function existingCompany(state: Company = null, action): Company {
|
||||
if (!action.type.startsWith('EXISTING_COMPANY::')) {
|
||||
return state
|
||||
}
|
||||
|
@ -150,11 +160,10 @@ function existingCompany(
|
|||
return state
|
||||
}
|
||||
|
||||
// $FlowFixMe
|
||||
export default (combineReducers({
|
||||
export default combineReducers({
|
||||
companyLegalStatus,
|
||||
companyStatusChoice,
|
||||
companyCreationChecklist,
|
||||
existingCompany,
|
||||
hiringChecklist
|
||||
}): (State, Action) => State)
|
||||
})
|
|
@ -1,19 +1,33 @@
|
|||
|
||||
import { Action } from 'Actions/actions'
|
||||
import { findRuleByDottedName } from 'Engine/rules'
|
||||
import { compose, defaultTo, dissoc, identity, lensPath, omit, over, set, uniq, without } from 'ramda'
|
||||
import {
|
||||
compose,
|
||||
defaultTo,
|
||||
dissoc,
|
||||
identity,
|
||||
lensPath,
|
||||
omit,
|
||||
over,
|
||||
set,
|
||||
uniq,
|
||||
without
|
||||
} from 'ramda'
|
||||
import reduceReducers from 'reduce-reducers'
|
||||
import { combineReducers } from 'redux'
|
||||
import { combineReducers, Reducer } from 'redux'
|
||||
import { targetNamesSelector } from 'Selectors/analyseSelectors'
|
||||
import i18n from '../i18n'
|
||||
import { SavedSimulation } from 'Selectors/storageSelectors'
|
||||
import { DottedName, Rule } from 'Types/rule'
|
||||
import i18n, { AvailableLangs } from '../i18n'
|
||||
import inFranceAppReducer from './inFranceAppReducer'
|
||||
import storageRootReducer from './storageReducer'
|
||||
|
||||
import type { Action } from 'Types/ActionsTypes'
|
||||
|
||||
function explainedVariable(state = null, { type, variableName = null }) {
|
||||
switch (type) {
|
||||
function explainedVariable(
|
||||
state: DottedName = null,
|
||||
action: Action
|
||||
): DottedName {
|
||||
switch (action.type) {
|
||||
case 'EXPLAIN_VARIABLE':
|
||||
return variableName
|
||||
return action.variableName
|
||||
case 'STEP_ACTION':
|
||||
return null
|
||||
default:
|
||||
|
@ -21,28 +35,32 @@ function explainedVariable(state = null, { type, variableName = null }) {
|
|||
}
|
||||
}
|
||||
|
||||
function currentExample(state = null, { type, situation, name, dottedName }) {
|
||||
switch (type) {
|
||||
function currentExample(state = null, action: Action) {
|
||||
switch (action.type) {
|
||||
case 'SET_EXAMPLE':
|
||||
const { situation, name, dottedName } = action
|
||||
return name != null ? { name, situation, dottedName } : null
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
function situationBranch(state = null, { type, id }) {
|
||||
switch (type) {
|
||||
function situationBranch(state: number = null, action: Action): number {
|
||||
switch (action.type) {
|
||||
case 'SET_SITUATION_BRANCH':
|
||||
return id
|
||||
return action.id
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
function activeTargetInput(state = null, { type, name }) {
|
||||
switch (type) {
|
||||
function activeTargetInput(
|
||||
state: DottedName | null = null,
|
||||
action: Action
|
||||
): DottedName | null {
|
||||
switch (action.type) {
|
||||
case 'SET_ACTIVE_TARGET_INPUT':
|
||||
return name
|
||||
return action.name
|
||||
case 'RESET_SIMULATION':
|
||||
return null
|
||||
default:
|
||||
|
@ -50,7 +68,10 @@ function activeTargetInput(state = null, { type, name }) {
|
|||
}
|
||||
}
|
||||
|
||||
function lang(state = i18n.language, { type, lang }) {
|
||||
function lang(
|
||||
state = i18n.language as AvailableLangs,
|
||||
{ type, lang }
|
||||
): AvailableLangs {
|
||||
switch (type) {
|
||||
case 'SWITCH_LANG':
|
||||
return lang
|
||||
|
@ -59,10 +80,10 @@ function lang(state = i18n.language, { type, lang }) {
|
|||
}
|
||||
}
|
||||
|
||||
type ConversationSteps = {|
|
||||
+foldedSteps: Array < string >,
|
||||
+unfoldedStep: ?string
|
||||
|}
|
||||
type ConversationSteps = {
|
||||
foldedSteps: Array<string>
|
||||
unfoldedStep?: string
|
||||
}
|
||||
|
||||
function conversationSteps(
|
||||
state: ConversationSteps = {
|
||||
|
@ -91,7 +112,7 @@ function conversationSteps(
|
|||
}
|
||||
|
||||
function updateSituation(situation, { fieldName, value, config, rules }) {
|
||||
const goals = targetNamesSelector({ simulation: { config } }).filter(
|
||||
const goals = targetNamesSelector({ simulation: { config } } as any).filter(
|
||||
dottedName => {
|
||||
const target = rules.find(r => r.dottedName === dottedName)
|
||||
const isSmallTarget = !target.question || !target.formule
|
||||
|
@ -115,14 +136,16 @@ function updatePeriod(situation, { toPeriod, rules }) {
|
|||
|
||||
const needConversion = Object.keys(situation).filter(dottedName => {
|
||||
const rule = findRuleByDottedName(rules, dottedName)
|
||||
return rule ?.période === 'flexible'
|
||||
return rule?.période === 'flexible'
|
||||
})
|
||||
|
||||
const updatedSituation = Object.entries(situation)
|
||||
.filter(([fieldName]) => needConversion.includes(fieldName))
|
||||
.map(([fieldName, value]) => [
|
||||
fieldName,
|
||||
currentPeriod === 'mois' && toPeriod === 'année' ? value * 12 : value / 12
|
||||
currentPeriod === 'mois' && toPeriod === 'année'
|
||||
? (value as number) * 12
|
||||
: (value as number) / 12
|
||||
])
|
||||
|
||||
return {
|
||||
|
@ -132,7 +155,34 @@ function updatePeriod(situation, { toPeriod, rules }) {
|
|||
}
|
||||
}
|
||||
|
||||
function simulation(state = null, action, rules) {
|
||||
type QuestionsKind =
|
||||
| "à l'affiche"
|
||||
| 'non prioritaires'
|
||||
| 'uniquement'
|
||||
| 'liste noire'
|
||||
|
||||
export type SimulationConfig = Partial<{
|
||||
objectifs:
|
||||
| Array<DottedName>
|
||||
| Array<{ icône: string; nom: string; objectifs: Array<DottedName> }>
|
||||
questions: Partial<Record<QuestionsKind, Array<DottedName>>>
|
||||
bloquant: Array<DottedName>
|
||||
situation: Simulation['situation']
|
||||
branches: Array<{ nom: string; situation: SimulationConfig['situation'] }>
|
||||
}>
|
||||
|
||||
export type Simulation = {
|
||||
config: SimulationConfig
|
||||
url: string
|
||||
hiddenControls: Array<string>
|
||||
situation: Record<DottedName, any>
|
||||
}
|
||||
|
||||
function simulation(
|
||||
state: Simulation = null,
|
||||
action: Action,
|
||||
rules: Array<Rule>
|
||||
): Simulation | null {
|
||||
if (action.type === 'SET_SIMULATION') {
|
||||
const { config, url } = action
|
||||
return { config, url, hiddenControls: [], situation: {} }
|
||||
|
@ -167,23 +217,33 @@ function simulation(state = null, action, rules) {
|
|||
return state
|
||||
}
|
||||
|
||||
const addAnswerToSituation = (dottedName, value, state) => {
|
||||
return compose(
|
||||
const addAnswerToSituation = (
|
||||
dottedName: DottedName,
|
||||
value: any,
|
||||
state: RootState
|
||||
) => {
|
||||
return (compose(
|
||||
set(lensPath(['simulation', 'situation', dottedName]), value),
|
||||
over(lensPath(['conversationSteps', 'foldedSteps']), (steps = []) =>
|
||||
uniq([...steps, dottedName])
|
||||
)
|
||||
)(state)
|
||||
) as any
|
||||
) as any)(state)
|
||||
}
|
||||
|
||||
const removeAnswerFromSituation = (dottedName, state) => {
|
||||
return compose(
|
||||
const removeAnswerFromSituation = (
|
||||
dottedName: DottedName,
|
||||
state: RootState
|
||||
) => {
|
||||
return (compose(
|
||||
over(lensPath(['simulation', 'situation']), dissoc(dottedName)),
|
||||
over(lensPath(['conversationSteps', 'foldedSteps']), without([dottedName]))
|
||||
)(state)
|
||||
over(
|
||||
lensPath(['conversationSteps', 'foldedSteps']),
|
||||
without([dottedName])
|
||||
) as any
|
||||
) as any)(state)
|
||||
}
|
||||
|
||||
const existingCompanyRootReducer = (state, action) => {
|
||||
const existingCompanyRootReducer = (state: RootState, action): RootState => {
|
||||
if (!action.type.startsWith('EXISTING_COMPANY::')) {
|
||||
return state
|
||||
}
|
||||
|
@ -200,21 +260,26 @@ const existingCompanyRootReducer = (state, action) => {
|
|||
return state
|
||||
}
|
||||
|
||||
const mainReducer = (state, action: Action) =>
|
||||
combineReducers({
|
||||
conversationSteps,
|
||||
lang,
|
||||
rules: defaultTo(null) as Reducer<Array<Rule>>,
|
||||
explainedVariable,
|
||||
// We need to access the `rules` in the simulation reducer
|
||||
simulation: (a: Simulation | null, b: Action) =>
|
||||
simulation(a, b, state.rules),
|
||||
previousSimulation: defaultTo(null) as Reducer<SavedSimulation>,
|
||||
currentExample,
|
||||
situationBranch,
|
||||
activeTargetInput,
|
||||
inFranceApp: inFranceAppReducer
|
||||
})(state, action)
|
||||
|
||||
export default reduceReducers(
|
||||
existingCompanyRootReducer,
|
||||
storageRootReducer,
|
||||
(state, action) =>
|
||||
combineReducers({
|
||||
conversationSteps,
|
||||
lang,
|
||||
rules: defaultTo(null),
|
||||
explainedVariable,
|
||||
// We need to access the `rules` in the simulation reducer
|
||||
simulation: (a, b) => simulation(a, b, state.rules),
|
||||
previousSimulation: defaultTo(null),
|
||||
currentExample,
|
||||
situationBranch,
|
||||
activeTargetInput,
|
||||
inFranceApp: inFranceAppReducer
|
||||
})(state, action)
|
||||
mainReducer
|
||||
)
|
||||
|
||||
export type RootState = ReturnType<typeof mainReducer>
|
|
@ -1,9 +1,8 @@
|
|||
|
||||
import type { State } from 'Types/State'
|
||||
import type { Action } from 'Types/ActionsTypes'
|
||||
import { Action } from 'Actions/actions'
|
||||
import { createStateFromSavedSimulation } from 'Selectors/storageSelectors'
|
||||
import { RootState } from './rootReducer'
|
||||
|
||||
export default (state: State, action: Action): State => {
|
||||
export default (state: RootState, action: Action): RootState => {
|
||||
switch (action.type) {
|
||||
case 'LOAD_PREVIOUS_SIMULATION':
|
||||
return {
|
|
@ -1,63 +1,38 @@
|
|||
import {
|
||||
collectMissingVariablesByTarget,
|
||||
getNextSteps
|
||||
} from 'Engine/generateQuestions'
|
||||
import {
|
||||
collectDefaults,
|
||||
disambiguateExampleSituation,
|
||||
findRuleByDottedName
|
||||
} from 'Engine/rules'
|
||||
import { collectMissingVariablesByTarget, getNextSteps } from 'Engine/generateQuestions'
|
||||
import { collectDefaults, disambiguateExampleSituation, findRuleByDottedName } from 'Engine/rules'
|
||||
import { analyse, analyseMany, parseAll } from 'Engine/traverse'
|
||||
import {
|
||||
add,
|
||||
defaultTo,
|
||||
difference,
|
||||
dissoc,
|
||||
equals,
|
||||
head,
|
||||
intersection,
|
||||
isEmpty,
|
||||
isNil,
|
||||
last,
|
||||
length,
|
||||
map,
|
||||
mergeDeepWith,
|
||||
negate,
|
||||
pick,
|
||||
pipe,
|
||||
sortBy,
|
||||
split,
|
||||
takeWhile,
|
||||
zipWith
|
||||
} from 'ramda'
|
||||
import { add, defaultTo, difference, dissoc, equals, head, intersection, isEmpty, isNil, last, length, map, mergeDeepWith, negate, pick, pipe, sortBy, split, takeWhile, zipWith } from 'ramda'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { RootState } from 'Reducers/rootReducer'
|
||||
import { createSelector, createSelectorCreator, defaultMemoize } from 'reselect'
|
||||
import { DottedName } from "Types/rule"
|
||||
import { mapOrApply } from '../utils'
|
||||
// create a "selector creator" that uses deep equal instead of ===
|
||||
const createDeepEqualSelector = createSelectorCreator(defaultMemoize, equals)
|
||||
|
||||
// We must here compute parsedRules, flatRules, analyse which contains both targets and cache objects
|
||||
export let flatRulesSelector = (state, props) => {
|
||||
export let flatRulesSelector = (
|
||||
state: RootState,
|
||||
props?: { rules: RootState['rules'] }
|
||||
) => {
|
||||
return props?.rules || state?.rules
|
||||
}
|
||||
|
||||
export let parsedRulesSelector = createSelector(
|
||||
[flatRulesSelector],
|
||||
rules => parseAll(rules)
|
||||
export let parsedRulesSelector = createSelector([flatRulesSelector], rules =>
|
||||
parseAll(rules)
|
||||
)
|
||||
|
||||
export let ruleDefaultsSelector = createSelector(
|
||||
[flatRulesSelector],
|
||||
rules => collectDefaults(rules)
|
||||
export let ruleDefaultsSelector = createSelector([flatRulesSelector], rules =>
|
||||
collectDefaults(rules)
|
||||
)
|
||||
|
||||
export let targetNamesSelector = state => {
|
||||
export let targetNamesSelector = (state: RootState) => {
|
||||
let objectifs = state.simulation?.config.objectifs
|
||||
if (!objectifs || !Array.isArray(objectifs)) {
|
||||
return null
|
||||
}
|
||||
const targetNames = [].concat(
|
||||
...objectifs.map(objectifOrGroup =>
|
||||
...(objectifs as any).map(objectifOrGroup =>
|
||||
typeof objectifOrGroup === 'string'
|
||||
? [objectifOrGroup]
|
||||
: objectifOrGroup.objectifs
|
||||
|
@ -70,17 +45,16 @@ export let targetNamesSelector = state => {
|
|||
return [...targetNames, ...secondaryTargetNames]
|
||||
}
|
||||
|
||||
export let situationSelector = state => state.simulation?.situation || {}
|
||||
type SituationSelectorType = typeof situationSelector
|
||||
|
||||
export const useSituation = () => useSelector(situationSelector)
|
||||
export const situationSelector = (state: RootState) =>
|
||||
state.simulation?.situation || {}
|
||||
|
||||
export const useSituationValue = fieldName => useSituation()?.[fieldName]
|
||||
export const usePeriod = () => useSelector(situationSelector)['période']
|
||||
|
||||
export const usePeriod = () => useSituationValue('période')
|
||||
|
||||
export const useTarget = dottedName => {
|
||||
export const useTarget = (dottedName: DottedName) => {
|
||||
const targets = useSelector(
|
||||
state => analysisWithDefaultsSelector(state).targets
|
||||
(state: RootState) => analysisWithDefaultsSelector(state).targets
|
||||
)
|
||||
return targets?.find(t => t.dottedName === dottedName)
|
||||
}
|
||||
|
@ -119,10 +93,13 @@ let validatedStepsSelector = createSelector(
|
|||
[state => state.conversationSteps.foldedSteps, targetNamesSelector],
|
||||
(foldedSteps, targetNames) => [...foldedSteps, ...targetNames]
|
||||
)
|
||||
let branchesSelector = state => state.simulation?.config.branches
|
||||
let configSituationSelector = state => state.simulation?.config.situation || {}
|
||||
let branchesSelector = (state: RootState) => state.simulation?.config.branches
|
||||
let configSituationSelector = (state: RootState) =>
|
||||
state.simulation?.config.situation || {}
|
||||
|
||||
const createSituationBrancheSelector = situationSelector =>
|
||||
const createSituationBrancheSelector = (
|
||||
situationSelector: SituationSelectorType
|
||||
) =>
|
||||
createSelector(
|
||||
[situationSelector, branchesSelector, configSituationSelector],
|
||||
(situation, branches, configSituation) => {
|
||||
|
@ -209,13 +186,16 @@ export let exampleAnalysisSelector = createSelector(
|
|||
analyseRule(rules, dottedName, dottedName => situation[dottedName])
|
||||
)
|
||||
|
||||
let makeAnalysisSelector = situationSelector =>
|
||||
let makeAnalysisSelector = (situationSelector: SituationSelectorType) =>
|
||||
createDeepEqualSelector(
|
||||
[parsedRulesSelector, targetNamesSelector, situationSelector],
|
||||
(parsedRules, targetNames, situations) =>
|
||||
mapOrApply(
|
||||
situation =>
|
||||
analyseMany(parsedRules, targetNames)(dottedName => {
|
||||
analyseMany(
|
||||
parsedRules,
|
||||
targetNames
|
||||
)(dottedName => {
|
||||
return situation[dottedName]
|
||||
}),
|
||||
situations
|
||||
|
@ -259,11 +239,11 @@ let currentMissingVariablesByTargetSelector = createSelector(
|
|||
}
|
||||
)
|
||||
|
||||
const similarity = (rule1, rule2) =>
|
||||
const similarity = (rule1: DottedName, rule2: DottedName) =>
|
||||
pipe(
|
||||
map(defaultTo('')),
|
||||
map(split(' . ')),
|
||||
rules => zipWith(equals, ...rules),
|
||||
(rules: [string[], string[]]) => zipWith(equals, ...rules),
|
||||
takeWhile(Boolean),
|
||||
length,
|
||||
negate
|
|
@ -1,12 +1,20 @@
|
|||
/* @flow */
|
||||
import { SitePaths } from 'Components/utils/withSitePaths'
|
||||
import {
|
||||
add,
|
||||
any,
|
||||
countBy,
|
||||
difference,
|
||||
flatten,
|
||||
isNil,
|
||||
keys,
|
||||
map,
|
||||
mergeAll,
|
||||
mergeWith,
|
||||
sortBy
|
||||
} from 'ramda'
|
||||
import { LegalStatusRequirements, State } from 'Types/companyTypes'
|
||||
|
||||
import type { State, LegalStatusRequirements } from 'Types/companyTypes'
|
||||
import type { SitePaths } from 'Components/utils/withSitePaths'
|
||||
import { add, any, countBy, difference, flatten, isNil, map, mergeAll, mergeWith, sortBy } from 'ramda'
|
||||
|
||||
const LEGAL_STATUS_DETAILS: {
|
||||
[status: string]: Array<LegalStatusRequirements> | LegalStatusRequirements
|
||||
} = {
|
||||
const LEGAL_STATUS_DETAILS = {
|
||||
'auto-entrepreneur': {
|
||||
soleProprietorship: true,
|
||||
directorStatus: 'SELF_EMPLOYED',
|
||||
|
@ -48,7 +56,7 @@ const LEGAL_STATUS_DETAILS: {
|
|||
multipleAssociates: true,
|
||||
autoEntrepreneur: false
|
||||
},
|
||||
SARL: ([
|
||||
SARL: [
|
||||
{
|
||||
soleProprietorship: false,
|
||||
directorStatus: 'SELF_EMPLOYED',
|
||||
|
@ -63,7 +71,7 @@ const LEGAL_STATUS_DETAILS: {
|
|||
minorityDirector: true,
|
||||
autoEntrepreneur: false
|
||||
}
|
||||
]: Array<LegalStatusRequirements>),
|
||||
] as Array<LegalStatusRequirements>,
|
||||
EURL: {
|
||||
soleProprietorship: false,
|
||||
directorStatus: 'SELF_EMPLOYED',
|
||||
|
@ -79,18 +87,14 @@ const LEGAL_STATUS_DETAILS: {
|
|||
}
|
||||
}
|
||||
|
||||
export type LegalStatus = $Keys<typeof LEGAL_STATUS_DETAILS>
|
||||
type Question = $Keys<LegalStatusRequirements>
|
||||
export type LegalStatus = keyof typeof LEGAL_STATUS_DETAILS
|
||||
type Question = keyof LegalStatusRequirements
|
||||
|
||||
// $FlowFixMe
|
||||
const QUESTION_LIST: Array<Question> = Object.keys(
|
||||
// $FlowFixMe
|
||||
const QUESTION_LIST: Array<Question> = keys(
|
||||
mergeAll(flatten(Object.values(LEGAL_STATUS_DETAILS)))
|
||||
)
|
||||
|
||||
const isCompatibleStatusWith = (answers: LegalStatusRequirements) => (
|
||||
statusRequirements: LegalStatusRequirements
|
||||
): boolean => {
|
||||
const isCompatibleStatusWith = answers => (statusRequirements): boolean => {
|
||||
const stringify = map(x => (!isNil(x) ? JSON.stringify(x) : x))
|
||||
const answerCompatibility = Object.values(
|
||||
mergeWith(
|
||||
|
@ -105,46 +109,49 @@ const isCompatibleStatusWith = (answers: LegalStatusRequirements) => (
|
|||
}
|
||||
const possibleStatus = (
|
||||
answers: LegalStatusRequirements
|
||||
): { [LegalStatus]: boolean } =>
|
||||
): Record<LegalStatus, boolean> =>
|
||||
map(
|
||||
statusRequirements =>
|
||||
Array.isArray(statusRequirements)
|
||||
? any(isCompatibleStatusWith(answers), statusRequirements)
|
||||
: isCompatibleStatusWith(answers)(statusRequirements),
|
||||
: isCompatibleStatusWith(answers)(
|
||||
statusRequirements as LegalStatusRequirements
|
||||
),
|
||||
LEGAL_STATUS_DETAILS
|
||||
)
|
||||
|
||||
export const possibleStatusSelector = (state: {
|
||||
inFranceApp: State
|
||||
}): { [LegalStatus]: boolean } =>
|
||||
}): Record<LegalStatus, boolean> =>
|
||||
possibleStatus(state.inFranceApp.companyLegalStatus)
|
||||
|
||||
export const nextQuestionSelector = (state: {
|
||||
inFranceApp: State
|
||||
}): ?Question => {
|
||||
}): Question => {
|
||||
const legalStatusRequirements = state.inFranceApp.companyLegalStatus
|
||||
const questionAnswered = Object.keys(legalStatusRequirements)
|
||||
const questionAnswered = Object.keys(legalStatusRequirements) as Array<
|
||||
Question
|
||||
>
|
||||
const possibleStatusList = flatten(
|
||||
Object.values(LEGAL_STATUS_DETAILS)
|
||||
// $FlowFixMe
|
||||
).filter(isCompatibleStatusWith(legalStatusRequirements))
|
||||
|
||||
const unansweredQuestions = difference(QUESTION_LIST, questionAnswered)
|
||||
const shannonEntropyByQuestion = unansweredQuestions.map(question => {
|
||||
const shannonEntropyByQuestion = unansweredQuestions.map((question): [
|
||||
typeof question,
|
||||
number
|
||||
] => {
|
||||
const answerPopulation = Object.values(possibleStatusList).map(
|
||||
// $FlowFixMe
|
||||
status => status[question]
|
||||
)
|
||||
const frequencyOfAnswers = Object.values(
|
||||
countBy(x => x, answerPopulation.filter(x => x !== undefined))
|
||||
).map(
|
||||
numOccurrence =>
|
||||
// $FlowFixMe
|
||||
numOccurrence / answerPopulation.length
|
||||
)
|
||||
countBy(
|
||||
x => x,
|
||||
answerPopulation.filter(x => x !== undefined)
|
||||
)
|
||||
).map(numOccurrence => numOccurrence / answerPopulation.length)
|
||||
const shannonEntropy = -frequencyOfAnswers
|
||||
.map(p => p * Math.log2(p))
|
||||
// $FlowFixMe
|
||||
.reduce(add, 0)
|
||||
return [question, shannonEntropy]
|
||||
})
|
|
@ -1,6 +1,7 @@
|
|||
import { RootState } from 'Reducers/rootReducer'
|
||||
import { nextStepsSelector } from './analyseSelectors'
|
||||
|
||||
export const simulationProgressSelector = state => {
|
||||
export const simulationProgressSelector = (state: RootState) => {
|
||||
const numberQuestionAnswered = state.conversationSteps.foldedSteps.length
|
||||
const numberQuestionLeft = nextStepsSelector(state).length
|
||||
return numberQuestionAnswered / (numberQuestionAnswered + numberQuestionLeft)
|
|
@ -1,23 +0,0 @@
|
|||
/* @flow */
|
||||
import type { SavedSimulation, State } from 'Types/State.js'
|
||||
|
||||
export const currentSimulationSelector: State => SavedSimulation = state => {
|
||||
return {
|
||||
situation: state.simulation.situation,
|
||||
activeTargetInput: state.activeTargetInput,
|
||||
foldedSteps: state.conversationSteps.foldedSteps
|
||||
}
|
||||
}
|
||||
|
||||
export const createStateFromSavedSimulation = state =>
|
||||
state.previousSimulation && {
|
||||
activeTargetInput: state.previousSimulation.activeTargetInput,
|
||||
simulation: {
|
||||
...state.simulation,
|
||||
situation: state.previousSimulation.situation || {}
|
||||
},
|
||||
conversationSteps: {
|
||||
foldedSteps: state.previousSimulation.foldedSteps
|
||||
},
|
||||
previousSimulation: null
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
import { RootState } from 'Reducers/rootReducer'
|
||||
|
||||
// Note: it is currently not possible to define SavedSimulation as the return
|
||||
// type of the currentSimulationSelector function because the type would then
|
||||
// circulary reference itself.
|
||||
export type SavedSimulation = {
|
||||
situation: RootState['simulation']['situation']
|
||||
activeTargetInput: RootState['activeTargetInput']
|
||||
foldedSteps: RootState['conversationSteps']['foldedSteps']
|
||||
}
|
||||
|
||||
export const currentSimulationSelector = (
|
||||
state: RootState
|
||||
): SavedSimulation => {
|
||||
return {
|
||||
situation: state.simulation.situation,
|
||||
activeTargetInput: state.activeTargetInput,
|
||||
foldedSteps: state.conversationSteps.foldedSteps
|
||||
}
|
||||
}
|
||||
|
||||
export const createStateFromSavedSimulation = (state: RootState) =>
|
||||
state.previousSimulation && {
|
||||
activeTargetInput: state.previousSimulation.activeTargetInput,
|
||||
simulation: {
|
||||
...state.simulation,
|
||||
situation: state.previousSimulation.situation || {}
|
||||
},
|
||||
conversationSteps: {
|
||||
foldedSteps: state.previousSimulation.foldedSteps
|
||||
},
|
||||
previousSimulation: null
|
||||
}
|
|
@ -15,7 +15,7 @@ const rewrite = basename => ({
|
|||
app.get('/', function(req, res) {
|
||||
res.send(`<ul><li><a href="/mon-entreprise">mon-entreprise [fr]</a></li>
|
||||
<li><a href="/infrance">infrance [en]</a></li>
|
||||
<li><a href="/mon-entreprise/dev/integration-test">intégration du simulateur sur site tiers [iframe fr]</a></li><li><a href="/publicodes">publicodes</a></li><</ul>`)
|
||||
<li><a href="/mon-entreprise/dev/integration-test">intégration du simulateur sur site tiers [iframe fr]</a></li><li><a href="/publicodes">publicodes</a></li></ul>`)
|
||||
})
|
||||
|
||||
app.use(
|
||||
|
|
|
@ -5,5 +5,4 @@ import 'regenerator-runtime/runtime'
|
|||
import App from './App'
|
||||
|
||||
let anchor = document.querySelector('#js')
|
||||
|
||||
render(<App language="en" basename="infrance" />, anchor)
|
||||
|
|
|
@ -8,7 +8,7 @@ import emoji from 'react-easy-emoji'
|
|||
import { Helmet } from 'react-helmet'
|
||||
import { Link } from 'react-router-dom'
|
||||
import SocialIcon from 'Ui/SocialIcon'
|
||||
import i18n from '../../../../i18n'
|
||||
import i18n, { AvailableLangs } from '../../../../i18n'
|
||||
import { hrefLangLink } from '../../sitePaths'
|
||||
import './Footer.css'
|
||||
import Privacy from './Privacy'
|
||||
|
@ -25,7 +25,7 @@ const feedbackBlacklist = [
|
|||
const Footer = () => {
|
||||
const sitePaths = useContext(SitePathsContext)
|
||||
const hrefLink =
|
||||
hrefLangLink[i18n.language][
|
||||
hrefLangLink[i18n.language as AvailableLangs][
|
||||
decodeURIComponent(
|
||||
(process.env.NODE_ENV === 'production'
|
||||
? window.location.protocol + '//' + window.location.host
|
||||
|
@ -99,7 +99,8 @@ const Footer = () => {
|
|||
<a
|
||||
href={href}
|
||||
key={hrefLang}
|
||||
style={{ textDecoration: 'underline' }}>
|
||||
style={{ textDecoration: 'underline' }}
|
||||
>
|
||||
{hrefLang === 'fr' ? (
|
||||
<> Passer en français {emoji('🇫🇷')}</>
|
||||
) : hrefLang === 'en' ? (
|
||||
|
|
|
@ -30,7 +30,7 @@ export default function Privacy() {
|
|||
)
|
||||
}
|
||||
|
||||
export let PrivacyContent = ({ language }) => (
|
||||
export let PrivacyContent = ({ language }: { language: string }) => (
|
||||
<>
|
||||
<T k="privacyContent">
|
||||
<h1>Vie privée</h1>
|
||||
|
|
|
@ -46,7 +46,7 @@ export default (tracker: Tracker) => {
|
|||
...(action.type === 'UPDATE_PERIOD'
|
||||
? ['période', action.toPeriod]
|
||||
: [action.fieldName, action.value])
|
||||
] as any)
|
||||
])
|
||||
}
|
||||
if (action.type === 'START_CONVERSATION') {
|
||||
tracker.push([
|
||||
|
|
|
@ -5,13 +5,14 @@ import React, { useContext } from 'react'
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import { RootState } from 'Reducers/rootReducer'
|
||||
import Animate from 'Ui/animate'
|
||||
import siret from './siret.jpg'
|
||||
|
||||
export default function AfterRegistration() {
|
||||
const sitePaths = useContext(SitePathsContext)
|
||||
const statutChoisi = useSelector<any, any>(
|
||||
state => state.inFranceApp.companyStatusChoice
|
||||
const statutChoisi = useSelector(
|
||||
(state: RootState) => state.inFranceApp.companyStatusChoice
|
||||
)
|
||||
const { t } = useTranslation()
|
||||
const isAutoentrepreneur = statutChoisi.match('auto-entrepreneur')
|
||||
|
@ -24,7 +25,8 @@ export default function AfterRegistration() {
|
|||
to={sitePaths.créer.index}
|
||||
exact
|
||||
activeClassName="ui__ hide"
|
||||
className="ui__ simple small button">
|
||||
className="ui__ simple small button"
|
||||
>
|
||||
← <T>Retour à la création</T>
|
||||
</NavLink>
|
||||
</div>
|
||||
|
@ -76,7 +78,8 @@ export default function AfterRegistration() {
|
|||
statutChoisi && statutChoisi.match(/auto-entrepreneur|EI/)
|
||||
? { display: 'none' }
|
||||
: {}
|
||||
}>
|
||||
}
|
||||
>
|
||||
Il détermine aussi la convention collective applicable à
|
||||
l'entreprise, et en partie le taux de la cotisation accidents du
|
||||
travail et maladies professionnelles à payer.
|
||||
|
|
|
@ -12,6 +12,7 @@ import { Helmet } from 'react-helmet'
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { connect, useSelector } from 'react-redux'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { RootState } from 'Reducers/rootReducer'
|
||||
import * as Animate from 'Ui/animate'
|
||||
import { CheckItem, Checklist } from 'Ui/Checklist'
|
||||
import StatutDescription from './StatutDescription'
|
||||
|
@ -24,8 +25,8 @@ function CreateCompany({
|
|||
}) {
|
||||
const { t, i18n } = useTranslation()
|
||||
const sitePaths = useContext(SitePathsContext)
|
||||
const companyCreationChecklist = useSelector<any, any>(
|
||||
state => state.inFranceApp.companyCreationChecklist
|
||||
const companyCreationChecklist = useSelector(
|
||||
(state: RootState) => state.inFranceApp.companyCreationChecklist
|
||||
)
|
||||
|
||||
// TODO : add this logic inside selector
|
||||
|
@ -75,7 +76,8 @@ function CreateCompany({
|
|||
<div css="transform: translateY(2rem);">
|
||||
<button
|
||||
onClick={onStatusChange}
|
||||
className="ui__ simple small push-left button">
|
||||
className="ui__ simple small push-left button"
|
||||
>
|
||||
<T k="entreprise.retour">← Choisir un autre statut</T>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -100,7 +102,8 @@ function CreateCompany({
|
|||
key={statut}
|
||||
onInitialization={items => onChecklistInitialization(statut, items)}
|
||||
onItemCheck={x => onItemCheck}
|
||||
defaultChecked={companyCreationChecklist}>
|
||||
defaultChecked={companyCreationChecklist}
|
||||
>
|
||||
<CheckItem
|
||||
name="legalStatus"
|
||||
defaultChecked={true}
|
||||
|
@ -191,7 +194,8 @@ function CreateCompany({
|
|||
<span
|
||||
style={{
|
||||
display: multipleAssociates ? 'visible' : 'none'
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
Dans le cas d'une création d'entreprise avec plusieurs
|
||||
associés, il est recommandé de faire appel à un juriste pour
|
||||
les rédiger.{' '}
|
||||
|
@ -365,7 +369,8 @@ function CreateCompany({
|
|||
? 'https://www.autoentrepreneur.urssaf.fr/portail/accueil/creer-mon-auto-entreprise.html'
|
||||
: 'https://account.guichet-entreprises.fr/user/create'
|
||||
}
|
||||
target="blank">
|
||||
target="blank"
|
||||
>
|
||||
Faire la démarche en ligne
|
||||
</a>
|
||||
</div>
|
||||
|
@ -437,14 +442,16 @@ function CreateCompany({
|
|||
> * {
|
||||
flex: 1;
|
||||
}
|
||||
`}>
|
||||
`}
|
||||
>
|
||||
{isAutoentrepreneur && (
|
||||
<Link
|
||||
className="ui__ interactive card button-choice lighter-bg"
|
||||
to={{
|
||||
pathname: sitePaths.simulateurs['auto-entrepreneur'],
|
||||
state: { fromCréer: true }
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<T k="entreprise.ressources.simu.autoEntrepreneur">
|
||||
<p>Simulateur de revenus auto-entrepreneur</p>
|
||||
<small>
|
||||
|
@ -460,7 +467,8 @@ function CreateCompany({
|
|||
to={{
|
||||
pathname: sitePaths.simulateurs.indépendant,
|
||||
state: { fromCréer: true }
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<T k="entreprise.ressources.simu.indépendant">
|
||||
<p>Simulateur de cotisations indépendant</p>
|
||||
<small>
|
||||
|
@ -476,7 +484,8 @@ function CreateCompany({
|
|||
to={{
|
||||
pathname: sitePaths.simulateurs['assimilé-salarié'],
|
||||
state: { fromCréer: true }
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<T k="entreprise.ressources.simu.assimilé">
|
||||
<p>Simulateur de cotisations assimilé-salarié</p>
|
||||
<small>
|
||||
|
@ -488,7 +497,8 @@ function CreateCompany({
|
|||
)}
|
||||
<Link
|
||||
className="ui__ interactive card button-choice lighter-bg"
|
||||
to={sitePaths.créer.après}>
|
||||
to={sitePaths.créer.après}
|
||||
>
|
||||
<T k="entreprise.ressources.après">
|
||||
<p>Après la création</p>
|
||||
<small>
|
||||
|
@ -501,7 +511,8 @@ function CreateCompany({
|
|||
<a
|
||||
target="_blank"
|
||||
className="ui__ interactive card button-choice lighter-bg"
|
||||
href="https://www.urssaf.fr/portail/files/live/sites/urssaf/files/documents/SSI-Guide-Objectif-Entreprise.pdf">
|
||||
href="https://www.urssaf.fr/portail/files/live/sites/urssaf/files/documents/SSI-Guide-Objectif-Entreprise.pdf"
|
||||
>
|
||||
<p>Guide de création URSSAF </p>
|
||||
<small>
|
||||
Des conseils sur comment préparer son projet pour se lancer dans
|
||||
|
@ -519,14 +530,11 @@ function CreateCompany({
|
|||
)
|
||||
}
|
||||
|
||||
export default connect(
|
||||
null,
|
||||
{
|
||||
onChecklistInitialization: initializeCompanyCreationChecklist,
|
||||
onItemCheck: checkCompanyCreationItem,
|
||||
onStatusChange: goToCompanyStatusChoice
|
||||
}
|
||||
)(CreateCompany)
|
||||
export default connect(null, {
|
||||
onChecklistInitialization: initializeCompanyCreationChecklist,
|
||||
onItemCheck: checkCompanyCreationItem,
|
||||
onStatusChange: goToCompanyStatusChoice
|
||||
})(CreateCompany)
|
||||
|
||||
let StatutsExample = ({ statut }) => {
|
||||
const links = {
|
||||
|
|
|
@ -12,7 +12,11 @@ import {
|
|||
} from 'Selectors/companyStatusSelectors'
|
||||
import StatutDescription from '../StatutDescription'
|
||||
|
||||
const StatutButton = ({ statut }: { statut: LegalStatus }) => {
|
||||
type StatutButtonProps = {
|
||||
statut: LegalStatus
|
||||
}
|
||||
|
||||
const StatutButton = ({ statut }: StatutButtonProps) => {
|
||||
const sitePaths = useContext(SitePathsContext)
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
|
@ -29,7 +33,12 @@ const StatutButton = ({ statut }: { statut: LegalStatus }) => {
|
|||
)
|
||||
}
|
||||
|
||||
const StatutTitle = ({ statut, language }) =>
|
||||
type StatutTitleProps = {
|
||||
statut: LegalStatus
|
||||
language: string
|
||||
}
|
||||
|
||||
const StatutTitle = ({ statut, language }: StatutTitleProps) =>
|
||||
statut === 'EI' ? (
|
||||
<>
|
||||
Entreprise individuelle {language !== 'fr' && '(Individual business)'}:{' '}
|
||||
|
@ -61,7 +70,7 @@ const StatutTitle = ({ statut, language }) =>
|
|||
</>
|
||||
) : statut === 'SA' ? (
|
||||
<>SA - Société anonyme {language !== 'fr' && '(Anonymous company)'}: </>
|
||||
) : statut === 'SNC' ? (
|
||||
) : (statut as string) === 'SNC' ? (
|
||||
<>SNC - Société en nom collectif {language !== 'fr' && '(Partnership)'}: </>
|
||||
) : statut === 'auto-entrepreneur' ? (
|
||||
<>
|
||||
|
@ -98,7 +107,7 @@ export default function SetMainStatus() {
|
|||
</h2>
|
||||
|
||||
<ul>
|
||||
{Object.keys(filter(Boolean, possibleStatus as any)).map(
|
||||
{Object.keys(filter(Boolean, possibleStatus)).map(
|
||||
(statut: keyof typeof possibleStatus) => (
|
||||
<li key={statut}>
|
||||
<strong>
|
||||
|
|
|
@ -4,6 +4,7 @@ import { isNil } from 'ramda'
|
|||
import React, { useContext } from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { RootState } from 'Reducers/rootReducer'
|
||||
import { LegalStatusRequirements } from 'Types/companyTypes'
|
||||
|
||||
const requirementToText = (
|
||||
|
@ -15,9 +16,7 @@ const requirementToText = (
|
|||
return value ? <T>Plusieurs associés</T> : <T>Un seul associé</T>
|
||||
case 'soleProprietorship':
|
||||
return value ? (
|
||||
<T T k="responsabilité.bouton2">
|
||||
Entreprise individuelle
|
||||
</T>
|
||||
<T k="responsabilité.bouton2">Entreprise individuelle</T>
|
||||
) : (
|
||||
<T k="responsabilité.bouton1">Société</T>
|
||||
)
|
||||
|
@ -36,8 +35,8 @@ const requirementToText = (
|
|||
|
||||
export default function PreviousAnswers() {
|
||||
const sitePaths = useContext(SitePathsContext)
|
||||
const legalStatus = useSelector<any, any>(
|
||||
state => state.inFranceApp.companyLegalStatus
|
||||
const legalStatus = useSelector(
|
||||
(state: RootState) => state.inFranceApp.companyLegalStatus
|
||||
)
|
||||
return (
|
||||
!!Object.values(legalStatus).length && (
|
||||
|
|
|
@ -70,14 +70,16 @@ const SoleProprietorship = ({ isSoleProprietorship }) => {
|
|||
onClick={() => {
|
||||
isSoleProprietorship(true)
|
||||
}}
|
||||
className="ui__ button">
|
||||
className="ui__ button"
|
||||
>
|
||||
<T k="responsabilité.bouton2">Entreprise individuelle</T>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
isSoleProprietorship(false)
|
||||
}}
|
||||
className="ui__ button">
|
||||
className="ui__ button"
|
||||
>
|
||||
<T k="responsabilité.bouton1">Société</T>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -87,9 +89,6 @@ const SoleProprietorship = ({ isSoleProprietorship }) => {
|
|||
)
|
||||
}
|
||||
|
||||
export default compose(
|
||||
connect(
|
||||
null,
|
||||
{ isSoleProprietorship }
|
||||
)
|
||||
)(SoleProprietorship)
|
||||
export default compose(connect(null, { isSoleProprietorship }))(
|
||||
SoleProprietorship
|
||||
)
|
||||
|
|
|
@ -5,6 +5,7 @@ import { Helmet } from 'react-helmet'
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { RootState } from 'Reducers/rootReducer'
|
||||
import { nextQuestionUrlSelector } from 'Selectors/companyStatusSelectors'
|
||||
import Animate from 'Ui/animate'
|
||||
import créerSvg from './créer.svg'
|
||||
|
@ -12,11 +13,12 @@ import créerSvg from './créer.svg'
|
|||
export default function Créer() {
|
||||
const { t } = useTranslation()
|
||||
const sitePaths = useContext(SitePathsContext)
|
||||
const nextQuestionUrl = useSelector(state =>
|
||||
const nextQuestionUrl = useSelector((state: RootState) =>
|
||||
nextQuestionUrlSelector(state, { sitePaths })
|
||||
)
|
||||
const guideAlreadyStarted = useSelector<any, any>(
|
||||
state => !!Object.keys(state.inFranceApp.companyLegalStatus).length
|
||||
const guideAlreadyStarted = useSelector(
|
||||
(state: RootState) =>
|
||||
!!Object.keys(state.inFranceApp.companyLegalStatus).length
|
||||
)
|
||||
return (
|
||||
<Animate.fromBottom>
|
||||
|
@ -42,7 +44,8 @@ export default function Créer() {
|
|||
guideAlreadyStarted && nextQuestionUrl
|
||||
? nextQuestionUrl
|
||||
: sitePaths.créer.guideStatut.multipleAssociates
|
||||
}>
|
||||
}
|
||||
>
|
||||
{!guideAlreadyStarted
|
||||
? t('créer.cta.default', 'Trouver le bon statut')
|
||||
: t('créer.cta.continue', 'Continuer le guide')}
|
||||
|
@ -72,10 +75,12 @@ export default function Créer() {
|
|||
> * {
|
||||
flex: 1;
|
||||
}
|
||||
`}>
|
||||
`}
|
||||
>
|
||||
<Link
|
||||
className="ui__ interactive card button-choice lighter-bg"
|
||||
to={sitePaths.créer.guideStatut.liste}>
|
||||
to={sitePaths.créer.guideStatut.liste}
|
||||
>
|
||||
<T k="créer.ressources.listeStatuts">
|
||||
<p>Liste des statuts juridiques </p>
|
||||
<small>
|
||||
|
@ -89,7 +94,8 @@ export default function Créer() {
|
|||
to={{
|
||||
pathname: sitePaths.simulateurs.comparaison,
|
||||
state: { fromCréer: true }
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<T k="créer.ressources.comparaison">
|
||||
<p>Comparateur de régimes</p>
|
||||
<small>
|
||||
|
@ -101,7 +107,8 @@ export default function Créer() {
|
|||
|
||||
<Link
|
||||
className="ui__ interactive card button-choice lighter-bg"
|
||||
to={sitePaths.créer['auto-entrepreneur']}>
|
||||
to={sitePaths.créer['auto-entrepreneur']}
|
||||
>
|
||||
<T k="créer.ressources.autoEntrepreneur">
|
||||
<p>Démarche auto-entrepreneur</p>
|
||||
<small>
|
||||
|
|
|
@ -56,7 +56,7 @@ const StatutDescription = ({ statut }: Props) =>
|
|||
d'être coté en bourse (à partir de 7 actionnaires). Le capital social
|
||||
minimum est de 37.000 €.
|
||||
</T>
|
||||
) : statut === 'SNC' ? (
|
||||
) : (statut as string) === 'SNC' ? (
|
||||
<T k="formeJuridique.SNC">
|
||||
La responsabilité des associés pour les dettes de la société est solidaire
|
||||
(un seul associé peut être poursuivi pour la totalité de la dette) et
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
|
||||
import withColours, { ThemeColoursProvider } from 'Components/utils/withColours'
|
||||
import React, { Suspense, useState } from 'react'
|
||||
import {
|
||||
ThemeColoursContext,
|
||||
ThemeColoursProvider
|
||||
} from 'Components/utils/withColours'
|
||||
import React, { Suspense, useContext, useState } from 'react'
|
||||
import Home from '../Iframes/SimulateurEmbauche'
|
||||
let LazyColorPicker = React.lazy(() => import('./ColorPicker'))
|
||||
|
||||
const Couleur = ({ colours: { colour: defaultColour } }) => {
|
||||
export default function Couleur() {
|
||||
const { colour: defaultColour } = useContext(ThemeColoursContext)
|
||||
const [colour, setColour] = useState(defaultColour)
|
||||
return (
|
||||
<>
|
||||
|
@ -28,5 +31,3 @@ const Couleur = ({ colours: { colour: defaultColour } }) => {
|
|||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default withColours(Couleur)
|
||||
|
|
|
@ -14,7 +14,7 @@ export default function IntegrationTest() {
|
|||
)
|
||||
const [colour, setColour] = React.useState('#005aa1')
|
||||
const [version, setVersion] = React.useState(0)
|
||||
const domNode = React.useRef(null)
|
||||
const domNode = React.useRef<HTMLDivElement>(null)
|
||||
React.useEffect(() => {
|
||||
const script = document.createElement('script')
|
||||
script.id = 'script-monentreprise'
|
||||
|
@ -47,14 +47,16 @@ export default function IntegrationTest() {
|
|||
|
||||
<button
|
||||
className="ui__ button plain"
|
||||
onClick={() => setVersion(version + 1)}>
|
||||
onClick={() => setVersion(version + 1)}
|
||||
>
|
||||
{!version ? 'Visualiser le module' : 'Valider les changements'}
|
||||
</button>
|
||||
|
||||
<div
|
||||
css={`
|
||||
display: ${version > 0 ? 'block' : 'none'};
|
||||
`}>
|
||||
`}
|
||||
>
|
||||
<p>Code d'intégration </p>
|
||||
<IntegrationCode colour={colour} module={currentModule} />
|
||||
<div style={{ border: '2px dashed blue' }}>
|
||||
|
@ -99,7 +101,8 @@ export let IntegrationCode = ({
|
|||
#scriptColor {
|
||||
color: #2975d1;
|
||||
}
|
||||
`}>
|
||||
`}
|
||||
>
|
||||
<span>{'<'}</span>
|
||||
<em>
|
||||
script
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
import withSitePaths from 'Components/utils/withSitePaths'
|
||||
import React from 'react'
|
||||
import { generateSiteMap } from '../../sitePaths'
|
||||
import { SitePathsContext } from 'Components/utils/withSitePaths'
|
||||
import React, { useContext } from 'react'
|
||||
import { generateSiteMap, SitePathsType } from '../../sitePaths'
|
||||
|
||||
const SiteMap = ({ sitePaths }) => (
|
||||
<>
|
||||
<h1>Sitemap</h1>
|
||||
<pre>
|
||||
{generateSiteMap(sitePaths).map(path => (
|
||||
<span key={path}>
|
||||
{path}
|
||||
<br />
|
||||
</span>
|
||||
))}
|
||||
</pre>
|
||||
</>
|
||||
)
|
||||
|
||||
export default withSitePaths(SiteMap)
|
||||
export default function SiteMap() {
|
||||
const sitePaths = useContext(SitePathsContext)
|
||||
return (
|
||||
<>
|
||||
<h1>Sitemap</h1>
|
||||
<pre>
|
||||
{generateSiteMap(sitePaths as SitePathsType).map(path => (
|
||||
<span key={path}>
|
||||
{path}
|
||||
<br />
|
||||
</span>
|
||||
))}
|
||||
</pre>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -6,10 +6,8 @@ import React, { useContext } from 'react'
|
|||
import emoji from 'react-easy-emoji'
|
||||
import { useSelector } from 'react-redux'
|
||||
import examples from 'Règles/cas-types.yaml'
|
||||
import {
|
||||
parsedRulesSelector,
|
||||
ruleDefaultsSelector
|
||||
} from 'Selectors/analyseSelectors'
|
||||
import { parsedRulesSelector, ruleDefaultsSelector } from 'Selectors/analyseSelectors'
|
||||
import { DottedName } from "Types/rule"
|
||||
import './ExampleSituations.css'
|
||||
|
||||
export default function ExampleSituations() {
|
||||
|
@ -20,7 +18,7 @@ export default function ExampleSituations() {
|
|||
<T>Quelques exemples de salaires</T>
|
||||
</h1>
|
||||
<ul>
|
||||
{examples.map(ex => (
|
||||
{examples.map((ex: any) => (
|
||||
<Example ex={ex} key={ex.nom} />
|
||||
))}
|
||||
</ul>
|
||||
|
@ -29,14 +27,16 @@ export default function ExampleSituations() {
|
|||
}
|
||||
|
||||
const Example = function Example({ ex: { nom, situation } }) {
|
||||
const defaults = useSelector(ruleDefaultsSelector) as object
|
||||
const defaults = useSelector(ruleDefaultsSelector)
|
||||
const parsedRules = useSelector(parsedRulesSelector)
|
||||
const colours = useContext(ThemeColoursContext)
|
||||
let [total, net, netAprèsImpôts] = analyseMany(parsedRules, [
|
||||
'total',
|
||||
'net',
|
||||
'net après impôt'
|
||||
])(dottedName => ({ ...defaults, ...situation }[dottedName])).targets,
|
||||
])(
|
||||
(dottedName: DottedName) => ({ ...defaults, ...situation }[dottedName])
|
||||
).targets,
|
||||
figures = [
|
||||
total,
|
||||
{
|
||||
|
@ -60,7 +60,8 @@ const Example = function Example({ ex: { nom, situation } }) {
|
|||
<h3>{t.title}</h3>
|
||||
<span
|
||||
style={{ color: colours.textColourOnWhite }}
|
||||
className="figure">
|
||||
className="figure"
|
||||
>
|
||||
{Math.round(t.nodeValue)} €
|
||||
</span>
|
||||
</li>
|
||||
|
|
|
@ -3,208 +3,217 @@ import {
|
|||
initializeHiringChecklist
|
||||
} from 'Actions/hiringChecklistAction'
|
||||
import { T } from 'Components'
|
||||
import { compose } from 'ramda'
|
||||
import React from 'react'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { withTranslation } from 'react-i18next'
|
||||
import { connect } from 'react-redux'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { connect, useSelector } from 'react-redux'
|
||||
import { RootState } from 'Reducers/rootReducer'
|
||||
import Animate from 'Ui/animate'
|
||||
import { CheckItem, Checklist } from 'Ui/Checklist'
|
||||
import { CheckItem, Checklist, ChecklistProps } from 'Ui/Checklist'
|
||||
|
||||
const Embaucher = ({
|
||||
onChecklistInitialization,
|
||||
onItemCheck,
|
||||
hiringChecklist,
|
||||
t
|
||||
}) => (
|
||||
<Animate.fromBottom>
|
||||
<Helmet>
|
||||
<title>
|
||||
{t(['embauche.tâches.page.titre', 'Les formalités pour embaucher'])}
|
||||
</title>
|
||||
<meta
|
||||
name="description"
|
||||
content={t(
|
||||
'embauche.tâches.page.description',
|
||||
"Toutes les démarches nécessaires à l'embauche de votre premier salarié."
|
||||
)}
|
||||
/>
|
||||
</Helmet>
|
||||
<h1>
|
||||
<T k="embauche.tâches.titre">Les formalités pour embaucher</T>
|
||||
</h1>
|
||||
<p>
|
||||
<T k="embauche.tâches.description">
|
||||
Toutes les étapes nécessaires à l'embauche de votre premier employé.
|
||||
</T>
|
||||
</p>
|
||||
<Checklist
|
||||
onInitialization={onChecklistInitialization}
|
||||
onItemCheck={onItemCheck}
|
||||
defaultChecked={hiringChecklist}>
|
||||
<CheckItem
|
||||
name="contract"
|
||||
title={
|
||||
<T k="embauche.tâches.contrat.titre">
|
||||
Signer un contrat de travail avec votre employé
|
||||
</T>
|
||||
}
|
||||
explanations={
|
||||
<p>
|
||||
<a
|
||||
className="ui__ button"
|
||||
href="https://www.service-public.fr/particuliers/vosdroits/N19871"
|
||||
target="_blank">
|
||||
{' '}
|
||||
<T>Plus d'informations</T>
|
||||
</a>
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
<CheckItem
|
||||
name="dpae"
|
||||
title={
|
||||
<T k="embauche.tâches.dpae.titre">
|
||||
Déclarer l'embauche à l'administration sociale
|
||||
</T>
|
||||
}
|
||||
explanations={
|
||||
<p>
|
||||
<T k="embauche.tâches.dpae.description">
|
||||
Ceci peut être fait par le biais du formulaire appelé DPAE, doit
|
||||
être complété dans les 8 jours avant toute embauche, et peut{' '}
|
||||
<a href="https://www.due.urssaf.fr" target="_blank">
|
||||
être effectué en ligne
|
||||
</a>
|
||||
.
|
||||
</T>
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
<CheckItem
|
||||
name="paySoftware"
|
||||
title={
|
||||
<T k="embauche.tâches.logiciel de paie.titre">
|
||||
Choisir un logiciel de paie
|
||||
</T>
|
||||
}
|
||||
explanations={
|
||||
<p>
|
||||
<T k="embauche.tâches.logiciel de paie.description">
|
||||
Les fiches de paie et les déclarations peuvent être traitées en
|
||||
ligne gratuitement par le{' '}
|
||||
<a href="http://www.letese.urssaf.fr" target="_blank">
|
||||
Tese
|
||||
</a>
|
||||
. Vous pouvez aussi utiliser un{' '}
|
||||
<a
|
||||
href="http://www.dsn-info.fr/convention-charte.htm"
|
||||
target="_blank">
|
||||
logiciel de paie privé.
|
||||
</a>
|
||||
</T>
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
<CheckItem
|
||||
name="registre"
|
||||
title={
|
||||
<T k="embauche.tâches.registre.titre">
|
||||
Tenir un registre des employés à jour
|
||||
</T>
|
||||
}
|
||||
explanations={
|
||||
<p>
|
||||
<a
|
||||
href="https://www.service-public.fr/professionnels-entreprises/vosdroits/F1784"
|
||||
className="ui__ button"
|
||||
target="_blank">
|
||||
<T>Plus d'informations</T>
|
||||
</a>
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
<CheckItem
|
||||
name="complementaryPension"
|
||||
title={
|
||||
<T k="embauche.tâches.pension.titre">
|
||||
Prendre contact avec l'institution de prévoyance complémentaire
|
||||
obligatoire qui vous est assignée
|
||||
</T>
|
||||
}
|
||||
explanations={
|
||||
<p>
|
||||
<a
|
||||
href="https://www.espace-entreprise.agirc-arrco.fr/simape/#/donneesDep"
|
||||
className="ui__ button"
|
||||
target="_blank">
|
||||
<T k="embauche.tâches.pension.description">
|
||||
Trouver mon institution de prévoyance
|
||||
</T>
|
||||
</a>
|
||||
{/* // The AGIRC-ARRCO complementary pension is mandatory. Those are only federations,{' '} */}
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
<CheckItem
|
||||
name="complementaryHealth"
|
||||
title={
|
||||
<T k="embauche.tâches.complémentaire santé.titre">
|
||||
Choisir une complémentaire santé
|
||||
</T>
|
||||
}
|
||||
explanations={
|
||||
<p>
|
||||
<T k="embauche.tâches.complémentaire santé.description">
|
||||
Vous devez couvrir vos salariés avec l'assurance complémentaire
|
||||
santé privée de votre choix (aussi appelée "mutuelle"), pour
|
||||
autant qu'elle offre un ensemble de garanties minimales.
|
||||
L'employeur doit payer au moins la moitié du forfait.
|
||||
</T>
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
<CheckItem
|
||||
name="workMedicine"
|
||||
title={
|
||||
<T k="embauche.tâches.medecine.titre">
|
||||
S'inscrire à un bureau de médecine du travail
|
||||
</T>
|
||||
}
|
||||
explanations={
|
||||
<p>
|
||||
<T k="embauche.tâches.medecine.description">
|
||||
N'oubliez pas de planifier un rendez-vous initial pour chaque
|
||||
nouvelle embauche.{' '}
|
||||
<a href="https://www.service-public.fr/particuliers/vosdroits/F2211">
|
||||
Plus d'infos.
|
||||
</a>
|
||||
</T>
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
</Checklist>
|
||||
<T k="embauche.chaque mois">
|
||||
<h2>Tous les mois</h2>
|
||||
<ul>
|
||||
<li>
|
||||
Utiliser un logiciel de paie pour calculer les cotisations sociales et
|
||||
les transmettre via la déclaration sociale nominative (DSN)
|
||||
</li>
|
||||
<li>Remettre la fiche de paie à votre employé</li>
|
||||
</ul>
|
||||
</T>
|
||||
</Animate.fromBottom>
|
||||
)
|
||||
type EmbaucherProps = {
|
||||
onChecklistInitialization: ChecklistProps['onInitialization']
|
||||
onItemCheck: ChecklistProps['onItemCheck']
|
||||
}
|
||||
|
||||
export default compose(
|
||||
withTranslation(),
|
||||
|
||||
connect(
|
||||
state => ({ hiringChecklist: (state as any).inFranceApp.hiringChecklist }),
|
||||
{
|
||||
onChecklistInitialization: initializeHiringChecklist,
|
||||
onItemCheck: checkHiringItem
|
||||
}
|
||||
function Embaucher({ onChecklistInitialization, onItemCheck }: EmbaucherProps) {
|
||||
const { t } = useTranslation()
|
||||
const hiringChecklist = useSelector(
|
||||
(state: RootState) => state.inFranceApp.hiringChecklist
|
||||
)
|
||||
return (
|
||||
<Animate.fromBottom>
|
||||
<Helmet>
|
||||
<title>
|
||||
{t(['embauche.tâches.page.titre', 'Les formalités pour embaucher'])}
|
||||
</title>
|
||||
<meta
|
||||
name="description"
|
||||
content={t(
|
||||
'embauche.tâches.page.description',
|
||||
"Toutes les démarches nécessaires à l'embauche de votre premier salarié."
|
||||
)}
|
||||
/>
|
||||
</Helmet>
|
||||
<h1>
|
||||
<T k="embauche.tâches.titre">Les formalités pour embaucher</T>
|
||||
</h1>
|
||||
<p>
|
||||
<T k="embauche.tâches.description">
|
||||
Toutes les étapes nécessaires à l'embauche de votre premier employé.
|
||||
</T>
|
||||
</p>
|
||||
<Checklist
|
||||
onInitialization={onChecklistInitialization}
|
||||
onItemCheck={onItemCheck}
|
||||
defaultChecked={hiringChecklist}
|
||||
>
|
||||
<CheckItem
|
||||
name="contract"
|
||||
title={
|
||||
<T k="embauche.tâches.contrat.titre">
|
||||
Signer un contrat de travail avec votre employé
|
||||
</T>
|
||||
}
|
||||
explanations={
|
||||
<p>
|
||||
<a
|
||||
className="ui__ button"
|
||||
href="https://www.service-public.fr/particuliers/vosdroits/N19871"
|
||||
target="_blank"
|
||||
>
|
||||
{' '}
|
||||
<T>Plus d'informations</T>
|
||||
</a>
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
<CheckItem
|
||||
name="dpae"
|
||||
title={
|
||||
<T k="embauche.tâches.dpae.titre">
|
||||
Déclarer l'embauche à l'administration sociale
|
||||
</T>
|
||||
}
|
||||
explanations={
|
||||
<p>
|
||||
<T k="embauche.tâches.dpae.description">
|
||||
Ceci peut être fait par le biais du formulaire appelé DPAE, doit
|
||||
être complété dans les 8 jours avant toute embauche, et peut{' '}
|
||||
<a href="https://www.due.urssaf.fr" target="_blank">
|
||||
être effectué en ligne
|
||||
</a>
|
||||
.
|
||||
</T>
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
<CheckItem
|
||||
name="paySoftware"
|
||||
title={
|
||||
<T k="embauche.tâches.logiciel de paie.titre">
|
||||
Choisir un logiciel de paie
|
||||
</T>
|
||||
}
|
||||
explanations={
|
||||
<p>
|
||||
<T k="embauche.tâches.logiciel de paie.description">
|
||||
Les fiches de paie et les déclarations peuvent être traitées en
|
||||
ligne gratuitement par le{' '}
|
||||
<a href="http://www.letese.urssaf.fr" target="_blank">
|
||||
Tese
|
||||
</a>
|
||||
. Vous pouvez aussi utiliser un{' '}
|
||||
<a
|
||||
href="http://www.dsn-info.fr/convention-charte.htm"
|
||||
target="_blank"
|
||||
>
|
||||
logiciel de paie privé.
|
||||
</a>
|
||||
</T>
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
<CheckItem
|
||||
name="registre"
|
||||
title={
|
||||
<T k="embauche.tâches.registre.titre">
|
||||
Tenir un registre des employés à jour
|
||||
</T>
|
||||
}
|
||||
explanations={
|
||||
<p>
|
||||
<a
|
||||
href="https://www.service-public.fr/professionnels-entreprises/vosdroits/F1784"
|
||||
className="ui__ button"
|
||||
target="_blank"
|
||||
>
|
||||
<T>Plus d'informations</T>
|
||||
</a>
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
<CheckItem
|
||||
name="complementaryPension"
|
||||
title={
|
||||
<T k="embauche.tâches.pension.titre">
|
||||
Prendre contact avec l'institution de prévoyance complémentaire
|
||||
obligatoire qui vous est assignée
|
||||
</T>
|
||||
}
|
||||
explanations={
|
||||
<p>
|
||||
<a
|
||||
href="https://www.espace-entreprise.agirc-arrco.fr/simape/#/donneesDep"
|
||||
className="ui__ button"
|
||||
target="_blank"
|
||||
>
|
||||
<T k="embauche.tâches.pension.description">
|
||||
Trouver mon institution de prévoyance
|
||||
</T>
|
||||
</a>
|
||||
{/* // The AGIRC-ARRCO complementary pension is mandatory. Those are only federations,{' '} */}
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
<CheckItem
|
||||
name="complementaryHealth"
|
||||
title={
|
||||
<T k="embauche.tâches.complémentaire santé.titre">
|
||||
Choisir une complémentaire santé
|
||||
</T>
|
||||
}
|
||||
explanations={
|
||||
<p>
|
||||
<T k="embauche.tâches.complémentaire santé.description">
|
||||
Vous devez couvrir vos salariés avec l'assurance complémentaire
|
||||
santé privée de votre choix (aussi appelée "mutuelle"), pour
|
||||
autant qu'elle offre un ensemble de garanties minimales.
|
||||
L'employeur doit payer au moins la moitié du forfait.
|
||||
</T>
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
<CheckItem
|
||||
name="workMedicine"
|
||||
title={
|
||||
<T k="embauche.tâches.medecine.titre">
|
||||
S'inscrire à un bureau de médecine du travail
|
||||
</T>
|
||||
}
|
||||
explanations={
|
||||
<p>
|
||||
<T k="embauche.tâches.medecine.description">
|
||||
N'oubliez pas de planifier un rendez-vous initial pour chaque
|
||||
nouvelle embauche.{' '}
|
||||
<a href="https://www.service-public.fr/particuliers/vosdroits/F2211">
|
||||
Plus d'infos.
|
||||
</a>
|
||||
</T>
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
</Checklist>
|
||||
<T k="embauche.chaque mois">
|
||||
<h2>Tous les mois</h2>
|
||||
<ul>
|
||||
<li>
|
||||
Utiliser un logiciel de paie pour calculer les cotisations sociales
|
||||
et les transmettre via la déclaration sociale nominative (DSN)
|
||||
</li>
|
||||
<li>Remettre la fiche de paie à votre employé</li>
|
||||
</ul>
|
||||
</T>
|
||||
</Animate.fromBottom>
|
||||
)
|
||||
}
|
||||
|
||||
export default connect(
|
||||
(state: RootState) => ({
|
||||
hiringChecklist: state.inFranceApp.hiringChecklist
|
||||
}),
|
||||
{
|
||||
onChecklistInitialization: initializeHiringChecklist,
|
||||
onItemCheck: checkHiringItem
|
||||
}
|
||||
)(Embaucher)
|
||||
|
|
|
@ -1,19 +1,25 @@
|
|||
import { resetEntreprise, specifyIfAutoEntrepreneur, specifyIfDirigeantMajoritaire } from 'Actions/existingCompanyActions'
|
||||
import {
|
||||
resetEntreprise,
|
||||
specifyIfAutoEntrepreneur,
|
||||
specifyIfDirigeantMajoritaire
|
||||
} from 'Actions/existingCompanyActions'
|
||||
import { T } from 'Components'
|
||||
import CompanyDetails from 'Components/CompanyDetails'
|
||||
import FindCompany from 'Components/FindCompany'
|
||||
import Overlay from 'Components/Overlay'
|
||||
import { ScrollToTop } from 'Components/utils/Scroll'
|
||||
import { SitePathsContext } from 'Components/utils/withSitePaths'
|
||||
import React, { useContext, useEffect, useRef, useState } from "react"
|
||||
import React, { useContext, useEffect, useRef, useState } from 'react'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { Company } from 'Reducers/inFranceAppReducer'
|
||||
import { RootState } from 'Reducers/rootReducer'
|
||||
import * as Animate from 'Ui/animate'
|
||||
import businessPlan from './businessPlan.svg'
|
||||
|
||||
const infereRégimeFromCompanyDetails = company => {
|
||||
const infereRégimeFromCompanyDetails = (company: Company) => {
|
||||
if (!company) {
|
||||
return null
|
||||
}
|
||||
|
@ -39,10 +45,12 @@ const infereRégimeFromCompanyDetails = company => {
|
|||
|
||||
export default function SocialSecurity() {
|
||||
const { t } = useTranslation()
|
||||
const company = useSelector<any, any>(state => state.inFranceApp.existingCompany)
|
||||
const company = useSelector(
|
||||
(state: RootState) => state.inFranceApp.existingCompany
|
||||
)
|
||||
const sitePaths = useContext(SitePathsContext)
|
||||
const régime = infereRégimeFromCompanyDetails(company)
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
|
@ -76,7 +84,9 @@ export default function SocialSecurity() {
|
|||
</div>
|
||||
|
||||
<>
|
||||
<h2><T k="gérer.choix.titre">Que souhaitez-vous faire ?</T></h2>
|
||||
<h2>
|
||||
<T k="gérer.choix.titre">Que souhaitez-vous faire ?</T>
|
||||
</h2>
|
||||
{!!régime && (
|
||||
<Link
|
||||
className="ui__ interactive card button-choice lighter-bg"
|
||||
|
@ -86,11 +96,10 @@ export default function SocialSecurity() {
|
|||
state: {
|
||||
fromGérer: true
|
||||
}
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<T k="gérer.choix.revenus">
|
||||
<p>
|
||||
Calculer mon revenu net
|
||||
</p>
|
||||
<p>Calculer mon revenu net</p>
|
||||
<small>
|
||||
Estimez précisément le montant de vos cotisations grâce au
|
||||
simulateur {{ régime }} de l’URSSAF
|
||||
|
@ -107,19 +116,20 @@ export default function SocialSecurity() {
|
|||
state: {
|
||||
fromGérer: true
|
||||
}
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<T k="gérer.choix.embauche">
|
||||
<p>
|
||||
Estimer le montant d’une embauche
|
||||
</p>
|
||||
<p>Estimer le montant d’une embauche</p>
|
||||
<small>
|
||||
Calculez le montant total que votre entreprise devra dépenser pour
|
||||
rémunérer votre prochain employé
|
||||
</small>
|
||||
Calculez le montant total que votre entreprise devra dépenser
|
||||
pour rémunérer votre prochain employé
|
||||
</small>
|
||||
</T>
|
||||
</Link>
|
||||
)}
|
||||
<h2><T>Ressources utiles</T></h2>
|
||||
<h2>
|
||||
<T>Ressources utiles</T>
|
||||
</h2>
|
||||
<div
|
||||
css={`
|
||||
display: flex;
|
||||
|
@ -128,45 +138,49 @@ export default function SocialSecurity() {
|
|||
> * {
|
||||
flex: 1;
|
||||
}
|
||||
`}>
|
||||
{!company ?.isAutoEntrepreneur && (
|
||||
`}
|
||||
>
|
||||
{!company?.isAutoEntrepreneur && (
|
||||
<Link
|
||||
className="ui__ interactive card button-choice lighter-bg"
|
||||
to={sitePaths.gérer.embaucher}>
|
||||
to={sitePaths.gérer.embaucher}
|
||||
>
|
||||
<T k="gérer.ressources.embaucher">
|
||||
|
||||
<p>Découvrir les démarches d’embauche </p>
|
||||
<small>
|
||||
La liste des choses à faire pour être sûr de ne rien oublier
|
||||
lors de l’embauche d’un nouveau salarié
|
||||
</small>
|
||||
</small>
|
||||
</T>
|
||||
</Link>
|
||||
)}
|
||||
{company ?.isAutoEntrepreneur && (
|
||||
{company?.isAutoEntrepreneur && (
|
||||
<a
|
||||
className="ui__ interactive card button-choice lighter-bg"
|
||||
href="https://autoentrepreneur.urssaf.fr">
|
||||
href="https://autoentrepreneur.urssaf.fr"
|
||||
>
|
||||
<T k="gérer.ressources.autoEntrepreneur">
|
||||
<p>Accéder au site officiel auto-entrepreneur</p>
|
||||
<small>
|
||||
Vous pourrez effectuer votre déclaration de chiffre d'affaire,
|
||||
payer vos cotisations, et plus largement trouver toutes les
|
||||
informations relatives au statut d'auto-entrepreneur
|
||||
</small>
|
||||
Vous pourrez effectuer votre déclaration de chiffre
|
||||
d'affaire, payer vos cotisations, et plus largement trouver
|
||||
toutes les informations relatives au statut
|
||||
d'auto-entrepreneur
|
||||
</small>
|
||||
</T>
|
||||
</a>
|
||||
)}
|
||||
<Link
|
||||
className="ui__ interactive card button-choice lighter-bg"
|
||||
to={sitePaths.gérer.sécuritéSociale}>
|
||||
to={sitePaths.gérer.sécuritéSociale}
|
||||
>
|
||||
<T k="gérer.ressources.sécuritéSociale">
|
||||
<p>Comprendre la sécurité sociale </p>
|
||||
<small>
|
||||
A quoi servent les cotisations sociales ? Le point sur le
|
||||
système de protection sociale dont bénéficient tous les
|
||||
travailleurs en France
|
||||
</small>
|
||||
</small>
|
||||
</T>
|
||||
</Link>
|
||||
</div>
|
||||
|
@ -176,7 +190,11 @@ export default function SocialSecurity() {
|
|||
)
|
||||
}
|
||||
|
||||
const CompanySection = ({ company }) => {
|
||||
type CompanySectionProps = {
|
||||
company: Company
|
||||
}
|
||||
|
||||
const CompanySection = ({ company }: CompanySectionProps) => {
|
||||
const [searchModal, showSearchModal] = useState(false)
|
||||
const [autoEntrepreneurModal, showAutoEntrepreneurModal] = useState(false)
|
||||
const [DirigeantMajoritaireModal, showDirigeantMajoritaireModal] = useState(
|
||||
|
@ -191,14 +209,14 @@ const CompanySection = ({ company }) => {
|
|||
showSearchModal(false)
|
||||
}
|
||||
if (
|
||||
company ?.statutJuridique === 'EI' &&
|
||||
company ?.isAutoEntrepreneur == null
|
||||
company?.statutJuridique === 'EI' &&
|
||||
company?.isAutoEntrepreneur == null
|
||||
) {
|
||||
showAutoEntrepreneurModal(true)
|
||||
}
|
||||
if (
|
||||
company ?.statutJuridique === 'SARL' &&
|
||||
company ?.isDirigeantMajoritaire == null
|
||||
company?.statutJuridique === 'SARL' &&
|
||||
company?.isDirigeantMajoritaire == null
|
||||
) {
|
||||
showDirigeantMajoritaireModal(true)
|
||||
}
|
||||
|
@ -206,11 +224,11 @@ const CompanySection = ({ company }) => {
|
|||
}, [company, searchModal])
|
||||
|
||||
const dispatch = useDispatch()
|
||||
const handleAnswerAutoEntrepreneur = isAutoEntrepreneur => {
|
||||
const handleAnswerAutoEntrepreneur = (isAutoEntrepreneur: boolean) => {
|
||||
dispatch(specifyIfAutoEntrepreneur(isAutoEntrepreneur))
|
||||
showAutoEntrepreneurModal(false)
|
||||
}
|
||||
const handleAnswerDirigeantMajoritaire = DirigeantMajoritaire => {
|
||||
const handleAnswerDirigeantMajoritaire = (DirigeantMajoritaire: boolean) => {
|
||||
dispatch(specifyIfDirigeantMajoritaire(DirigeantMajoritaire))
|
||||
showDirigeantMajoritaireModal(false)
|
||||
}
|
||||
|
@ -221,16 +239,20 @@ const CompanySection = ({ company }) => {
|
|||
<>
|
||||
<ScrollToTop />
|
||||
<Overlay>
|
||||
<h2><T k="gérer.entreprise.auto">Êtes-vous auto-entrepreneur ? </T></h2>
|
||||
<h2>
|
||||
<T k="gérer.entreprise.auto">Êtes-vous auto-entrepreneur ? </T>
|
||||
</h2>
|
||||
<div className="ui__ answer-group">
|
||||
<button
|
||||
className="ui__ button"
|
||||
onClick={() => handleAnswerAutoEntrepreneur(true)}>
|
||||
onClick={() => handleAnswerAutoEntrepreneur(true)}
|
||||
>
|
||||
<T>Oui</T>
|
||||
</button>
|
||||
<button
|
||||
className="ui__ button"
|
||||
onClick={() => handleAnswerAutoEntrepreneur(false)}>
|
||||
onClick={() => handleAnswerAutoEntrepreneur(false)}
|
||||
>
|
||||
<T>Non</T>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -252,12 +274,14 @@ const CompanySection = ({ company }) => {
|
|||
<div className="ui__ answer-group">
|
||||
<button
|
||||
className="ui__ button"
|
||||
onClick={() => handleAnswerDirigeantMajoritaire(true)}>
|
||||
onClick={() => handleAnswerDirigeantMajoritaire(true)}
|
||||
>
|
||||
<T>Oui</T>
|
||||
</button>
|
||||
<button
|
||||
className="ui__ button"
|
||||
onClick={() => handleAnswerDirigeantMajoritaire(false)}>
|
||||
onClick={() => handleAnswerDirigeantMajoritaire(false)}
|
||||
>
|
||||
<T>Non</T>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -286,9 +310,15 @@ const CompanySection = ({ company }) => {
|
|||
</span>
|
||||
{company.isDirigeantMajoritaire != null && (
|
||||
<span css="margin-left: 1rem" className="ui__ label">
|
||||
{company.isDirigeantMajoritaire
|
||||
? <T k="gérer.entreprise.majoritaire">Dirigeant majoritaire</T>
|
||||
: <T k="gérer.entreprise.minoritaire">Dirigeant minoritaire</T>}
|
||||
{company.isDirigeantMajoritaire ? (
|
||||
<T k="gérer.entreprise.majoritaire">
|
||||
Dirigeant majoritaire
|
||||
</T>
|
||||
) : (
|
||||
<T k="gérer.entreprise.minoritaire">
|
||||
Dirigeant minoritaire
|
||||
</T>
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
|
@ -299,19 +329,23 @@ const CompanySection = ({ company }) => {
|
|||
onClick={() => {
|
||||
dispatch(resetEntreprise())
|
||||
showSearchModal(true)
|
||||
}}>
|
||||
<T k="gérer.entreprise.changer">Changer l'entreprise sélectionnée</T>
|
||||
}}
|
||||
>
|
||||
<T k="gérer.entreprise.changer">
|
||||
Changer l'entreprise sélectionnée
|
||||
</T>
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<p>
|
||||
<button
|
||||
onClick={() => showSearchModal(true)}
|
||||
className="ui__ plain cta button">
|
||||
<T k="gérer.cta">Renseigner mon entreprise</T>
|
||||
</button>
|
||||
</p>
|
||||
)}
|
||||
<p>
|
||||
<button
|
||||
onClick={() => showSearchModal(true)}
|
||||
className="ui__ plain cta button"
|
||||
>
|
||||
<T k="gérer.cta">Renseigner mon entreprise</T>
|
||||
</button>
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { T } from 'Components'
|
||||
import animate from 'Ui/animate'
|
||||
import { SitePathsContext } from 'Components/utils/withSitePaths'
|
||||
import React, { useContext } from 'react'
|
||||
import emoji from 'react-easy-emoji'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Link } from 'react-router-dom'
|
||||
import animate from 'Ui/animate'
|
||||
|
||||
export default function SchemeChoice() {
|
||||
const sitePaths = useContext(SitePathsContext)
|
||||
|
@ -21,7 +21,8 @@ export default function SchemeChoice() {
|
|||
<p>
|
||||
<Link
|
||||
to={sitePaths.simulateurs['assimilé-salarié']}
|
||||
className="ui__ interactive card light-bg button-choice">
|
||||
className="ui__ interactive card light-bg button-choice"
|
||||
>
|
||||
{emoji('☂')}
|
||||
<span>
|
||||
<T>Assimilé salarié</T>
|
||||
|
@ -36,7 +37,8 @@ export default function SchemeChoice() {
|
|||
</Link>
|
||||
<Link
|
||||
to={sitePaths.simulateurs.indépendant}
|
||||
className="ui__ interactive card light-bg button-choice">
|
||||
className="ui__ interactive card light-bg button-choice"
|
||||
>
|
||||
{emoji('👩🔧')}
|
||||
<span>
|
||||
<T>Indépendant</T>
|
||||
|
@ -51,7 +53,8 @@ export default function SchemeChoice() {
|
|||
</Link>
|
||||
<Link
|
||||
to={sitePaths.simulateurs['auto-entrepreneur']}
|
||||
className="ui__ interactive card light-bg button-choice">
|
||||
className="ui__ interactive card light-bg button-choice"
|
||||
>
|
||||
{emoji('🚶♂️')}
|
||||
Auto-entrepreneur
|
||||
</Link>
|
||||
|
@ -64,7 +67,8 @@ export default function SchemeChoice() {
|
|||
<p style={{ textAlign: 'center', marginTop: '1rem' }}>
|
||||
<Link
|
||||
className="ui__ plain cta button"
|
||||
to={sitePaths.simulateurs.comparaison}>
|
||||
to={sitePaths.simulateurs.comparaison}
|
||||
>
|
||||
<T k="selectionRégime.comparer.cta">Comparer les régimes</T>
|
||||
</Link>
|
||||
</p>
|
||||
|
|
|
@ -19,7 +19,8 @@ export default function Gérer() {
|
|||
to={sitePaths.gérer.index}
|
||||
exact
|
||||
activeClassName="ui__ hide"
|
||||
className="ui__ simple push-left small button">
|
||||
className="ui__ simple push-left small button"
|
||||
>
|
||||
← <T>Retour à mon activité</T>
|
||||
</NavLink>
|
||||
</div>
|
||||
|
|
|
@ -7,7 +7,6 @@ import screenfull from 'screenfull'
|
|||
import { isIE } from '../../../../utils'
|
||||
import Privacy from '../../layout/Footer/Privacy'
|
||||
|
||||
|
||||
export default function IframeFooter() {
|
||||
const [isFullscreen, setIsFullscreen] = useState(screenfull.isFullscreen)
|
||||
useEffect(() => {
|
||||
|
@ -25,14 +24,16 @@ export default function IframeFooter() {
|
|||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between'
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<LangSwitcher className="ui__ button simple" />
|
||||
{screenfull.enabled && !isFullscreen && !isIE() && (
|
||||
<button
|
||||
className="ui__ button small"
|
||||
onClick={() => {
|
||||
screenfull.toggle()
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
{emoji('🖵')}
|
||||
<Trans>Plein écran</Trans>
|
||||
</button>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { SitePathsContext } from 'Components/utils/withSitePaths';
|
||||
import React, { useContext } from 'react';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { SalarySimulation } from '../Simulateurs/Salarié';
|
||||
import { SitePathsContext } from 'Components/utils/withSitePaths'
|
||||
import React, { useContext } from 'react'
|
||||
import { Helmet } from 'react-helmet'
|
||||
import { SalarySimulation } from '../Simulateurs/Salarié'
|
||||
|
||||
export default function IframeSimulateurEmbauche() {
|
||||
const sitePaths = useContext(SitePathsContext);
|
||||
const sitePaths = useContext(SitePathsContext)
|
||||
return (
|
||||
<>
|
||||
<Helmet>
|
||||
|
|
|
@ -5,14 +5,15 @@ import React, { useContext } from 'react'
|
|||
import emoji from 'react-easy-emoji'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { RootState } from 'Reducers/rootReducer'
|
||||
import Footer from '../../layout/Footer/Footer'
|
||||
import illustrationSvg from './illustration.svg'
|
||||
import './Landing.css'
|
||||
|
||||
export default function Landing() {
|
||||
const sitePaths = useContext(SitePathsContext)
|
||||
const statutChoisi = useSelector<any, any>(
|
||||
state => state.inFranceApp.companyStatusChoice
|
||||
const statutChoisi = useSelector(
|
||||
(state: RootState) => state.inFranceApp.companyStatusChoice
|
||||
)
|
||||
return (
|
||||
<div className="app-content">
|
||||
|
@ -42,7 +43,8 @@ export default function Landing() {
|
|||
className="ui__ interactive card box"
|
||||
to={
|
||||
statutChoisi ? sitePaths.créer[statutChoisi] : sitePaths.créer.index
|
||||
}>
|
||||
}
|
||||
>
|
||||
<div className="ui__ big box-icon">{emoji('💡')}</div>
|
||||
<T k="landing.choice.create">
|
||||
<h3>Créer une entreprise</h3>
|
||||
|
@ -70,7 +72,8 @@ export default function Landing() {
|
|||
</Link>
|
||||
<Link
|
||||
className="ui__ interactive card box"
|
||||
to={sitePaths.économieCollaborative.index}>
|
||||
to={sitePaths.économieCollaborative.index}
|
||||
>
|
||||
<div className="ui__ big box-icon">{emoji('🙋')}</div>
|
||||
<T k="landing.choice.declare">
|
||||
<h3>Que dois-je déclarer ?</h3>
|
||||
|
@ -88,7 +91,8 @@ export default function Landing() {
|
|||
<div style={{ width: '100%', textAlign: 'center' }}>
|
||||
<Link
|
||||
to={sitePaths.simulateurs.index}
|
||||
className="ui__ simple small button ">
|
||||
className="ui__ simple small button "
|
||||
>
|
||||
{emoji('🧮')}{' '}
|
||||
<T k="landing.seeSimulators">Voir la liste des simulateurs</T>
|
||||
</Link>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue