📈 Stats page - indicateurs globaux
* Ajout des indicateurs globaux (depuis le début et 30 derniers jours) en haut de page * Améliore typage page Stats * Refactor SatisfactionChart fix #1473pull/1597/head
parent
2250dccf4b
commit
1e913a3a30
|
@ -30,6 +30,9 @@ Nous utilisons :
|
|||
|
||||
### Démarrage
|
||||
|
||||
Tout d'abord assurez-vous d'avoir toutes les clés d'API nécessaires dans votre fichier `mon-entreprise/.env`.
|
||||
Demandez les détails à vos collègues (ces informations n'étant pas publiques).
|
||||
|
||||
Si l'historique des commits est trop volumineux, vous pouvez utiliser le paramètre `depth` de git pour ne télécharger que les derniers commits.
|
||||
|
||||
```
|
||||
|
@ -39,6 +42,9 @@ git clone --depth 100 git@github.com:betagouv/mon-entreprise.git && cd mon-entre
|
|||
# Install the Javascript dependencies through Yarn
|
||||
yarn install
|
||||
|
||||
# Download some data
|
||||
yarn prepare
|
||||
|
||||
# Watch changes in publicodes and run the server for mon-entreprise
|
||||
yarn start
|
||||
```
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { AvailableLangs } from 'locales/i18n'
|
||||
import emoji from 'react-easy-emoji'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
|
@ -9,7 +10,7 @@ const languageCodeToEmoji = {
|
|||
export default function LangSwitcher({ className }: { className: string }) {
|
||||
const { i18n } = useTranslation()
|
||||
const languageCode = i18n.language
|
||||
const unusedLanguageCode =
|
||||
const unusedLanguageCode: AvailableLangs =
|
||||
!languageCode || languageCode === 'fr' ? 'en' : 'fr'
|
||||
const changeLanguage = () => {
|
||||
i18n.changeLanguage(unusedLanguageCode)
|
||||
|
@ -19,7 +20,7 @@ export default function LangSwitcher({ className }: { className: string }) {
|
|||
className={className ?? 'ui__ link-button'}
|
||||
onClick={changeLanguage}
|
||||
>
|
||||
{emoji(languageCodeToEmoji[languageCode as 'fr' | 'en'])}{' '}
|
||||
{emoji(languageCodeToEmoji[languageCode as AvailableLangs])}{' '}
|
||||
{languageCode.toUpperCase()}
|
||||
</button>
|
||||
)
|
||||
|
|
|
@ -0,0 +1,192 @@
|
|||
import emoji from 'react-easy-emoji'
|
||||
import { Indicators, Indicator } from './utils'
|
||||
import { SatisfactionLevel, StatsStruct } from './types'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { SatisfactionStyle } from './SatisfactionChart'
|
||||
|
||||
const add = (a: number, b: number) => a + b
|
||||
const lastCompare = (startDate: Date, dateStr: string) =>
|
||||
startDate < new Date(dateStr)
|
||||
|
||||
const BigIndicator: typeof Indicator = ({ main, subTitle, footnote }) => (
|
||||
<Indicator
|
||||
main={
|
||||
<div
|
||||
css={`
|
||||
font-size: 2rem;
|
||||
line-height: 3rem;
|
||||
`}
|
||||
>
|
||||
{main}
|
||||
</div>
|
||||
}
|
||||
subTitle={subTitle}
|
||||
footnote={footnote}
|
||||
/>
|
||||
)
|
||||
|
||||
const RetoursAsProgress = ({
|
||||
percentages,
|
||||
}: {
|
||||
percentages: Record<SatisfactionLevel, number>
|
||||
}) => (
|
||||
<div
|
||||
className="progress__container"
|
||||
css={`
|
||||
width: 95%;
|
||||
height: 2.5rem;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
display: flex;
|
||||
font-size: 1.8rem;
|
||||
`}
|
||||
>
|
||||
{' '}
|
||||
{SatisfactionStyle.map(([level, { emoji: emojiStr, color }]) => (
|
||||
<div
|
||||
key={level}
|
||||
css={`
|
||||
width: ${percentages[level]}%;
|
||||
background-color: ${color};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`}
|
||||
>
|
||||
{emoji(emojiStr)}
|
||||
<div
|
||||
css={`
|
||||
position: absolute;
|
||||
margin-top: 4rem;
|
||||
font-size: 0.7rem;
|
||||
font-weight: lighter;
|
||||
`}
|
||||
>
|
||||
{Math.round(percentages[level])}%
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
export default function GlobalStats({ stats }: { stats: StatsStruct }) {
|
||||
const { i18n } = useTranslation()
|
||||
const formatNumber = Intl.NumberFormat(i18n.language).format.bind(null)
|
||||
|
||||
const totalVisits = formatNumber(
|
||||
stats.visitesMois.site.map(({ nombre }) => nombre).reduce(add, 0)
|
||||
)
|
||||
const totalCommenceATI = stats.visitesMois.pages
|
||||
.filter(({ page }) => page === 'simulation_commencee')
|
||||
.map(({ nombre }) => nombre)
|
||||
.reduce(add, 0)
|
||||
// Hardcoded stuff from https://github.com/betagouv/mon-entreprise/pull/1563#discussion_r635893624
|
||||
const totalCommenceMatomo = Object.values({
|
||||
2019: Math.floor((1262601 * 45) / 100),
|
||||
2020: 1373536,
|
||||
2021: 273731,
|
||||
}).reduce(add, 0)
|
||||
const totalCommence = formatNumber(totalCommenceMatomo + totalCommenceATI)
|
||||
|
||||
const day30before = new Date(new Date().setDate(new Date().getDate() - 30))
|
||||
|
||||
const last30dVisitsNum = stats.visitesJours.site
|
||||
.filter(({ date }) => lastCompare(day30before, date))
|
||||
.map(({ nombre }) => nombre)
|
||||
.reduce(add, 0)
|
||||
const last30dVisits = formatNumber(last30dVisitsNum)
|
||||
const last30dCommenceNum = stats.visitesJours.pages
|
||||
.filter(
|
||||
({ date, page }) =>
|
||||
lastCompare(day30before, date) && page === 'simulation_commencee'
|
||||
)
|
||||
.map(({ nombre }) => nombre)
|
||||
.reduce(add, 0)
|
||||
const last30dCommence = formatNumber(last30dCommenceNum)
|
||||
const last30dConv = Math.round((100 * last30dCommenceNum) / last30dVisitsNum)
|
||||
|
||||
const last30dSatisfactions = stats.satisfaction
|
||||
.filter(({ date }) => lastCompare(day30before, date))
|
||||
.reduce(
|
||||
(acc, { click: satisfactionLevel, nombre }) => ({
|
||||
...acc,
|
||||
[satisfactionLevel]: acc[satisfactionLevel] + nombre,
|
||||
}),
|
||||
{
|
||||
[SatisfactionLevel.Mauvais]: 0,
|
||||
[SatisfactionLevel.Moyen]: 0,
|
||||
[SatisfactionLevel.Bien]: 0,
|
||||
[SatisfactionLevel.TrèsBien]: 0,
|
||||
}
|
||||
)
|
||||
const last30dSatisfactionTotal = Object.values(last30dSatisfactions).reduce(
|
||||
(a, b) => a + b
|
||||
)
|
||||
const last30dSatisfactionPercentages = Object.fromEntries(
|
||||
Object.entries(last30dSatisfactions).map(([level, count]) => [
|
||||
level,
|
||||
(100 * count) / last30dSatisfactionTotal,
|
||||
])
|
||||
) as Record<SatisfactionLevel, number>
|
||||
|
||||
return (
|
||||
<>
|
||||
{' '}
|
||||
<Indicators>
|
||||
<BigIndicator
|
||||
main={totalVisits}
|
||||
subTitle="Visites"
|
||||
footnote="depuis le 1ᵉ janvier 2019"
|
||||
/>
|
||||
<BigIndicator
|
||||
main={totalCommence}
|
||||
subTitle="Simulations lancées"
|
||||
footnote="depuis le 1ᵉ janvier 2019"
|
||||
/>
|
||||
</Indicators>
|
||||
<Indicators>
|
||||
<BigIndicator
|
||||
main={last30dVisits}
|
||||
subTitle="Visites"
|
||||
footnote="sur les 30 derniers jours"
|
||||
/>
|
||||
<BigIndicator
|
||||
main={last30dCommence}
|
||||
subTitle="Simulations lancées"
|
||||
footnote="sur les 30 derniers jours"
|
||||
/>
|
||||
</Indicators>
|
||||
<div
|
||||
css={`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
margin: -1rem 0 0 0;
|
||||
`}
|
||||
>
|
||||
<i>
|
||||
<small>Taux de conversion vers une simulation :</small>{' '}
|
||||
<b>{last30dConv}%</b>
|
||||
</i>
|
||||
</div>
|
||||
<Indicators>
|
||||
<Indicator
|
||||
subTitle="Satisfaction utilisateurs"
|
||||
main={
|
||||
<div
|
||||
css={`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
`}
|
||||
>
|
||||
{' '}
|
||||
<RetoursAsProgress percentages={last30dSatisfactionPercentages} />
|
||||
</div>
|
||||
}
|
||||
footnote={`${last30dSatisfactionTotal} avis sur les 30 derniers jours`}
|
||||
width="75%"
|
||||
/>
|
||||
</Indicators>
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -1,6 +1,4 @@
|
|||
import { ThemeColorsContext } from 'Components/utils/colors'
|
||||
import { add, mapObjIndexed } from 'ramda'
|
||||
import React, { useContext } from 'react'
|
||||
import emoji from 'react-easy-emoji'
|
||||
import {
|
||||
Bar,
|
||||
|
@ -10,6 +8,22 @@ import {
|
|||
Tooltip,
|
||||
XAxis,
|
||||
} from 'recharts'
|
||||
import { SatisfactionLevel } from './types'
|
||||
|
||||
export const SatisfactionStyle: [
|
||||
SatisfactionLevel,
|
||||
{ emoji: string; color: string }
|
||||
][] = [
|
||||
[SatisfactionLevel.Mauvais, { emoji: '🙁', color: '#ff5959' }],
|
||||
[SatisfactionLevel.Moyen, { emoji: '😐', color: '#fff339' }],
|
||||
[SatisfactionLevel.Bien, { emoji: '🙂', color: '#90e789' }],
|
||||
[SatisfactionLevel.TrèsBien, { emoji: '😀', color: '#0fc700' }],
|
||||
]
|
||||
|
||||
function toPercentage(data: Record<string, number>): Record<string, number> {
|
||||
const total = Object.values(data).reduce(add)
|
||||
return { ...mapObjIndexed((value) => (100 * value) / total, data), total }
|
||||
}
|
||||
|
||||
type SatisfactionChartProps = {
|
||||
data: Array<{
|
||||
|
@ -17,13 +31,7 @@ type SatisfactionChartProps = {
|
|||
nombre: Record<string, number>
|
||||
}>
|
||||
}
|
||||
|
||||
function toPercentage(data: Record<string, number>): Record<string, number> {
|
||||
const total = Object.values(data).reduce(add)
|
||||
return { ...mapObjIndexed((value) => (100 * value) / total, data), total }
|
||||
}
|
||||
export default function SatisfactionChart({ data }: SatisfactionChartProps) {
|
||||
const { color, lightColor, lighterColor } = useContext(ThemeColorsContext)
|
||||
if (!data.length) {
|
||||
return null
|
||||
}
|
||||
|
@ -34,22 +42,21 @@ export default function SatisfactionChart({ data }: SatisfactionChartProps) {
|
|||
<BarChart data={flattenData}>
|
||||
<XAxis dataKey="date" tickFormatter={formatMonth} />
|
||||
<Tooltip content={<CustomTooltip />} />
|
||||
<Bar dataKey="mauvais" stackId="1" fill="#fd667f" maxBarSize={50}>
|
||||
<LabelList dataKey="mauvais" content={() => '🙁'} position="left" />
|
||||
</Bar>
|
||||
<Bar dataKey="moyen" stackId="1" maxBarSize={50} fill={lighterColor}>
|
||||
<LabelList dataKey="moyen" content={() => '😐'} position="left" />
|
||||
</Bar>
|
||||
<Bar dataKey="bien" stackId="1" maxBarSize={50} fill={lightColor}>
|
||||
<LabelList dataKey="bien" content={() => '🙂'} position="left" />
|
||||
</Bar>
|
||||
<Bar dataKey="très bien" stackId="1" maxBarSize={50} fill={color}>
|
||||
<LabelList
|
||||
dataKey="très bien"
|
||||
content={() => '😀'}
|
||||
position="left"
|
||||
/>
|
||||
</Bar>
|
||||
{SatisfactionStyle.map(([level, { emoji, color }]) => (
|
||||
<Bar
|
||||
key={level}
|
||||
dataKey={level}
|
||||
stackId="1"
|
||||
fill={color}
|
||||
maxBarSize={50}
|
||||
>
|
||||
<LabelList
|
||||
dataKey={level}
|
||||
content={() => emoji}
|
||||
position="left"
|
||||
/>
|
||||
</Bar>
|
||||
))}
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</>
|
||||
|
|
|
@ -10,18 +10,23 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
|||
import emoji from 'react-easy-emoji'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { useHistory, useLocation } from 'react-router-dom'
|
||||
import styled from 'styled-components'
|
||||
import { TrackPage } from '../../ATInternetTracking'
|
||||
import stats from '../../data/stats.json'
|
||||
import statsJson from '../../data/stats.json'
|
||||
import { debounce } from '../../utils'
|
||||
import { SimulateurCard } from '../Simulateurs/Home'
|
||||
import useSimulatorsData, { SimulatorData } from '../Simulateurs/metadata'
|
||||
import Chart from './Chart'
|
||||
import DemandeUtilisateurs from './DemandesUtilisateurs'
|
||||
import GlobalStats from './GlobalStats'
|
||||
import { formatDay, formatMonth, Indicators, Indicator } from './utils'
|
||||
import SatisfactionChart from './SatisfactionChart'
|
||||
import { StatsStruct, PageChapter2, Page, PageSatisfaction } from './types'
|
||||
|
||||
const stats = (statsJson as unknown) as StatsStruct
|
||||
|
||||
type Period = 'mois' | 'jours'
|
||||
type Chapter2 = typeof stats.visitesJours.pages[number]['page_chapter2'] | 'PAM'
|
||||
type Chapter2 = PageChapter2 | 'PAM'
|
||||
|
||||
const chapters2: Chapter2[] = [
|
||||
...new Set(stats.visitesMois.pages.map((p) => p.page_chapter2)),
|
||||
'PAM',
|
||||
|
@ -31,6 +36,8 @@ type Data =
|
|||
| Array<{ date: string; nombre: number }>
|
||||
| Array<{ date: string; nombre: Record<string, number> }>
|
||||
|
||||
type Pageish = Page & PageSatisfaction
|
||||
|
||||
const isPAM = (name: string | undefined) =>
|
||||
name &&
|
||||
[
|
||||
|
@ -39,23 +46,15 @@ const isPAM = (name: string | undefined) =>
|
|||
'auxiliaire_medical',
|
||||
'sage_femme',
|
||||
].includes(name)
|
||||
type RawData = Array<{
|
||||
date: string
|
||||
page_chapter1?: string
|
||||
page_chapter2: string
|
||||
page_chapter3?: string
|
||||
page?: string
|
||||
click?: string
|
||||
nombre: number
|
||||
}>
|
||||
|
||||
const filterByChapter2 = (
|
||||
data: RawData,
|
||||
chapter2: Chapter2
|
||||
pages: Pageish[],
|
||||
chapter2: Chapter2 | ''
|
||||
): Array<{ date: string; nombre: Record<string, number> }> => {
|
||||
return toPairs(
|
||||
groupBy(
|
||||
(p) => p.date,
|
||||
data.filter(
|
||||
pages.filter(
|
||||
(p) =>
|
||||
!chapter2 ||
|
||||
(p.page !== 'accueil_pamc' &&
|
||||
|
@ -72,7 +71,7 @@ const filterByChapter2 = (
|
|||
}))
|
||||
}
|
||||
|
||||
function groupByDate(data: RawData) {
|
||||
function groupByDate(data: Pageish[]) {
|
||||
return toPairs(
|
||||
groupBy(
|
||||
(p) => p.date,
|
||||
|
@ -102,7 +101,7 @@ const computeTotals = (data: Data): number | Record<string, number> => {
|
|||
.reduce(mergeWith(add), {})
|
||||
}
|
||||
|
||||
export default function Stats() {
|
||||
const StatsDetail = () => {
|
||||
const defaultPeriod = 'mois'
|
||||
const history = useHistory()
|
||||
const location = useLocation()
|
||||
|
@ -113,7 +112,7 @@ export default function Stats() {
|
|||
(urlParams.get('periode') as Period) ?? defaultPeriod
|
||||
)
|
||||
const [chapter2, setChapter2] = useState<Chapter2 | ''>(
|
||||
urlParams.get('module') ?? ''
|
||||
(urlParams.get('module') as Chapter2) ?? ''
|
||||
)
|
||||
|
||||
// The logic to persist some state in query parameters in the URL could be
|
||||
|
@ -134,16 +133,16 @@ export default function Stats() {
|
|||
if (!chapter2) {
|
||||
return rawData.site
|
||||
}
|
||||
return filterByChapter2(rawData.pages, chapter2)
|
||||
return filterByChapter2(rawData.pages as Pageish[], chapter2)
|
||||
}, [period, chapter2])
|
||||
|
||||
const repartition = useMemo(() => {
|
||||
const rawData = stats.visitesMois
|
||||
return groupByDate(rawData.pages)
|
||||
return groupByDate(rawData.pages as Pageish[])
|
||||
}, [])
|
||||
|
||||
const satisfaction = useMemo(() => {
|
||||
return filterByChapter2(stats.satisfaction, chapter2)
|
||||
return filterByChapter2(stats.satisfaction as Pageish[], chapter2)
|
||||
}, [chapter2])
|
||||
|
||||
const [[startDateIndex, endDateIndex], setDateIndex] = useState<
|
||||
|
@ -173,21 +172,10 @@ export default function Stats() {
|
|||
() => computeTotals(slicedVisits),
|
||||
[slicedVisits]
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<TrackPage chapter1="informations" name="stats" />
|
||||
<ScrollToTop />
|
||||
|
||||
<h1>
|
||||
Statistiques <>{emoji('📊')}</>
|
||||
</h1>
|
||||
<p>
|
||||
Découvrez nos statistiques d'utilisation mises à jour quotidiennement.
|
||||
<br />
|
||||
Les données recueillies sont anonymisées.{' '}
|
||||
<Privacy label="En savoir plus" />
|
||||
</p>
|
||||
|
||||
<h2>Statistiques détaillées</h2>
|
||||
<p>
|
||||
<strong>1. Sélectionner la fonctionnalité : </strong>
|
||||
</p>
|
||||
|
@ -315,57 +303,34 @@ export default function Stats() {
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<DemandeUtilisateurs />
|
||||
<MoreInfosOnUs />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const Indicators = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
margin: 2rem 0;
|
||||
`
|
||||
|
||||
type IndicatorProps = {
|
||||
main?: string
|
||||
subTitle?: React.ReactNode
|
||||
}
|
||||
|
||||
function Indicator({ main, subTitle }: IndicatorProps) {
|
||||
export default function Stats() {
|
||||
return (
|
||||
<div
|
||||
className="ui__ card lighter-bg"
|
||||
css={`
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
width: 210px;
|
||||
font-size: 110%;
|
||||
`}
|
||||
>
|
||||
<small>{subTitle}</small>
|
||||
<br />
|
||||
<strong>{main}</strong>
|
||||
</div>
|
||||
<>
|
||||
<TrackPage chapter1="informations" name="stats" />
|
||||
<ScrollToTop />
|
||||
|
||||
<h1>
|
||||
Statistiques <>{emoji('📊')}</>
|
||||
</h1>
|
||||
<p>
|
||||
Découvrez nos statistiques d'utilisation mises à jour quotidiennement.
|
||||
<br />
|
||||
Les données recueillies sont anonymisées.{' '}
|
||||
<Privacy label="En savoir plus" />
|
||||
</p>
|
||||
<GlobalStats stats={stats} />
|
||||
<StatsDetail />
|
||||
|
||||
<DemandeUtilisateurs />
|
||||
<MoreInfosOnUs />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function formatDay(date: string | Date) {
|
||||
return new Date(date).toLocaleString('default', {
|
||||
weekday: 'long',
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
})
|
||||
}
|
||||
|
||||
function formatMonth(date: string | Date) {
|
||||
return new Date(date).toLocaleString('default', {
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
})
|
||||
}
|
||||
|
||||
function getChapter2(s: SimulatorData[keyof SimulatorData]): Chapter2 | '' {
|
||||
if (s.iframePath === 'pamc') {
|
||||
return 'PAM'
|
||||
|
@ -373,9 +338,10 @@ function getChapter2(s: SimulatorData[keyof SimulatorData]): Chapter2 | '' {
|
|||
if (!s.tracking) {
|
||||
return ''
|
||||
}
|
||||
return typeof s.tracking === 'string' ? s.tracking : s.tracking.chapter2 ?? ''
|
||||
const tracking = s.tracking as { chapter2?: Chapter2 }
|
||||
return typeof tracking === 'string' ? tracking : tracking.chapter2 ?? ''
|
||||
}
|
||||
function SelectedSimulator(props: { chapter2: Chapter2 }) {
|
||||
function SelectedSimulator(props: { chapter2: Chapter2 | '' }) {
|
||||
const simulateur = Object.values(useSimulatorsData()).find(
|
||||
(s) => getChapter2(s) === props.chapter2 && !(s.tracking as any).chapter3
|
||||
)
|
||||
|
@ -440,7 +406,9 @@ function SimulateursChoice(props: {
|
|||
type="radio"
|
||||
name="simulateur"
|
||||
value={getChapter2(s)}
|
||||
onChange={(evt) => props.onChange(evt.target.value)}
|
||||
onChange={(evt) =>
|
||||
props.onChange(evt.target.value as Chapter2 | '')
|
||||
}
|
||||
checked={getChapter2(s) === props.value}
|
||||
/>
|
||||
<span>
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
import statsJson from '../../data/stats.json'
|
||||
|
||||
// Generated using app.quicktype.io
|
||||
|
||||
export interface StatsStruct {
|
||||
visitesJours: Visites
|
||||
visitesMois: Visites
|
||||
satisfaction: PageSatisfaction[]
|
||||
retoursUtilisateurs: RetoursUtilisateurs
|
||||
}
|
||||
|
||||
export interface RetoursUtilisateurs {
|
||||
open: Closed[]
|
||||
closed: Closed[]
|
||||
}
|
||||
|
||||
export interface Closed {
|
||||
title: string
|
||||
closedAt: string | null
|
||||
number: number
|
||||
count: number
|
||||
}
|
||||
|
||||
export interface BasePage {
|
||||
date: string
|
||||
nombre: number
|
||||
page_chapter1: string
|
||||
page_chapter2: PageChapter2
|
||||
page_chapter3: string
|
||||
}
|
||||
export type Page = BasePage & { page: string }
|
||||
export type PageSatisfaction = BasePage & { click: SatisfactionLevel }
|
||||
|
||||
export enum SatisfactionLevel {
|
||||
Bien = 'bien',
|
||||
Mauvais = 'mauvais',
|
||||
Moyen = 'moyen',
|
||||
TrèsBien = 'très bien',
|
||||
}
|
||||
|
||||
export interface Visites {
|
||||
pages: Page[]
|
||||
site: Site[]
|
||||
}
|
||||
|
||||
export interface Site {
|
||||
date: string
|
||||
nombre: number
|
||||
}
|
||||
|
||||
export enum PageChapter2 {
|
||||
AideDeclarationIndependant = 'aide_declaration_independant',
|
||||
ArtisteAuteur = 'artiste_auteur',
|
||||
AutoEntrepreneur = 'auto_entrepreneur',
|
||||
ChomagePartiel = 'chomage_partiel',
|
||||
ComparaisonStatut = 'comparaison_statut',
|
||||
DirigeantSasu = 'dirigeant_sasu',
|
||||
EconomieCollaborative = 'economie_collaborative',
|
||||
Guide = 'guide',
|
||||
ImpotSociete = 'impot_societe',
|
||||
Independant = 'independant',
|
||||
ProfessionLiberale = 'profession_liberale',
|
||||
Salarie = 'salarie',
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
export const Indicators = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-around;
|
||||
margin: 2rem 0;
|
||||
`
|
||||
type IndicatorProps = {
|
||||
main?: React.ReactNode
|
||||
subTitle?: React.ReactNode
|
||||
footnote?: string
|
||||
width?: string
|
||||
}
|
||||
export function Indicator({ main, subTitle, footnote, width }: IndicatorProps) {
|
||||
return (
|
||||
<div
|
||||
className="ui__ card lighter-bg"
|
||||
css={`
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
width: ${width || '210px'};
|
||||
font-size: 110%;
|
||||
`}
|
||||
>
|
||||
<small
|
||||
css={`
|
||||
display: block;
|
||||
`}
|
||||
>
|
||||
{subTitle}
|
||||
</small>
|
||||
<strong
|
||||
css={`
|
||||
display: block;
|
||||
`}
|
||||
>
|
||||
{main}
|
||||
</strong>
|
||||
{footnote && (
|
||||
<span
|
||||
css={`
|
||||
font-size: small;
|
||||
display: block;
|
||||
`}
|
||||
>
|
||||
<i>{footnote}</i>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export function formatDay(date: string | Date) {
|
||||
return new Date(date).toLocaleString('default', {
|
||||
weekday: 'long',
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
})
|
||||
}
|
||||
export function formatMonth(date: string | Date) {
|
||||
return new Date(date).toLocaleString('default', {
|
||||
month: 'long',
|
||||
year: 'numeric',
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue