Ajoute la repartition des visites par page

pull/1411/head^2
Johan Girod 2021-03-15 18:39:13 +01:00
parent 28e29fb7b9
commit ce0e957a35
9 changed files with 3858 additions and 208 deletions

View File

@ -4,6 +4,9 @@ on:
types: [opened, synchronize]
env:
GITHUB_API_SECRET: ${{ secrets.GITHUB_TOKEN }}
ZAMMAD_API_SECRET_KEY: ${{ secrets.ZAMMAD_API_SECRET_KEY }}
ATINTERNET_API_SECRET_KEY: ${{ secrets.ATINTERNET_API_SECRET_KEY }}
ATINTERNET_API_ACCESS_KEY: ${{ secrets.ATINTERNET_API_ACCESS_KEY }}
INSEE_SIRENE_API_SECRET: ${{ secrets.INSEE_SIRENE_API_TOKEN }}
MATOMO_TOKEN: ${{ secrets.MATOMO_TOKEN }}
EN_SITE: "${path}?s=m"

View File

@ -265,4 +265,6 @@ async function main() {
})
}
main()
main().catch((e) => {
throw new Error(e)
})

File diff suppressed because it is too large Load Diff

View File

@ -173,6 +173,7 @@ Taux moyen: Average rate
Total des retenues: Total withheld
"Total payé par l'entreprise :": "Total paid by the company :"
Tout effacer: Delete all
Tout le site: The whole site
Tranche de l'assiette: Scale bracket
Un seul associé: Only one partner
Une idée&nbsp;?<1></1>Contactez-nous&nbsp;!: Any ideas?<1></1>Contact us!

View File

@ -1,17 +1,15 @@
import { ThemeColorsContext } from 'Components/utils/colors'
import { formatValue } from 'publicodes'
import { groupWith } from 'ramda'
import React, { useContext } from 'react'
import React, { Fragment, useContext } from 'react'
import {
Area,
AreaChart,
Bar,
BarChart,
Brush,
CartesianGrid,
ComposedChart,
Legend,
Line,
LineChart,
ReferenceArea,
ResponsiveContainer,
Tooltip,
XAxis,
@ -39,6 +37,11 @@ const weekEndDays = groupWith(
type VisitsChartProps = {
period: Period
sync?: boolean
stack?: boolean
grid?: boolean
colored?: boolean
layout?: 'horizontal' | 'vertical'
onDateChange?: ({
startIndex,
endIndex,
@ -50,28 +53,65 @@ type VisitsChartProps = {
endIndex?: number
data: Data
}
const Palette = [
'#1ea0f5',
'#697ad5',
'#b453b6',
'#ff2d96',
'#fd667f',
'#fc9e67',
'#fad750',
'#bed976',
'#82da9d',
'#46dcc3',
]
export default function VisitsChart({
period,
data,
onDateChange,
sync = true,
layout = 'horizontal',
grid = true,
stack = false,
colored = false,
startIndex,
endIndex,
}: VisitsChartProps) {
const { color, lightColor, lighterColor } = useContext(ThemeColorsContext)
const { darkColor, lightColor, lighterColor } = useContext(ThemeColorsContext)
if (!data.length) {
return null
}
const isStacked = typeof data[0].nombre !== 'number'
const isBarChart = data.length <= 3
const dataKeys = isStacked ? Object.keys(data[0].nombre) : ['nombre']
const flattenData = data.map((d) => (isStacked ? { ...d, ...d.nombre } : d))
const flattenData = (data as any).map((d: any) =>
isStacked ? { ...d, ...d.nombre } : d
)
const Chart = isBarChart ? BarChart : isStacked ? AreaChart : LineChart
const AxeA: any = layout === 'horizontal' ? XAxis : YAxis
const AxeB: any = layout === 'horizontal' ? YAxis : XAxis
function getColor(i: number): string {
if (!colored) {
return [lighterColor, lightColor, darkColor][i % 3]
}
return Palette[i % Palette.length]
}
return (
<>
<ResponsiveContainer width="100%" height={400}>
<Chart data={flattenData} syncId="1">
<div
css={`
svg {
overflow: visible;
}
`}
>
<ResponsiveContainer width="100%" height={500}>
<ComposedChart
layout={layout}
data={flattenData}
syncId={sync ? '1' : undefined}
>
{data.length > 1 && onDateChange && (
<Brush
startIndex={startIndex}
@ -81,65 +121,59 @@ export default function VisitsChart({
tickFormatter={period === 'jours' ? formatDay : formatMonth}
/>
)}
<CartesianGrid />
<XAxis
{grid && <CartesianGrid />}
<Legend />
<AxeA
dataKey="date"
type="category"
tickFormatter={period === 'jours' ? formatDay : formatMonth}
/>
<YAxis
domain={[0, 'auto']}
<AxeB
dataKey={dataKeys[0]}
tickFormatter={formatValue}
type="number"
/>
<Tooltip
content={<CustomTooltip period={period} dataKeys={dataKeys} />}
/>
{weekEndDays
.filter((days) => days.length === 2)
.map((days) => (
<ReferenceArea
key={days[0]}
x1={days[0]}
x2={days[1]}
strokeOpacity={0.3}
/>
))}
{dataKeys.map((k, i) =>
isBarChart ? (
<Bar
layout={layout}
key={k}
dataKey={k}
maxBarSize={50}
fill={
i % 3 === 2 ? color : i % 3 === 1 ? lightColor : lighterColor
}
name={formatLegend(k)}
barSize={20}
stackId={stack ? 1 : undefined}
fill={getColor(i)}
/>
) : isStacked ? (
<Area
key={k}
dataKey={k}
name={formatLegend(k)}
stackId={stack ? 1 : undefined}
type="monotone"
stroke={color}
fill={
i % 3 === 2 ? color : i % 3 === 1 ? lightColor : lighterColor
}
stroke={getColor(i)}
fill={getColor(i)}
/>
) : (
<Line
type="monotone"
dataKey={k}
stroke={color}
name={formatLegend(k)}
stroke={getColor(i + 1)}
strokeWidth={3}
animationDuration={500}
/>
)
)}
</Chart>
</ComposedChart>
</ResponsiveContainer>
</>
</div>
)
}
@ -150,6 +184,14 @@ function formatDay(date: string | Date) {
})
}
function formatDayLong(date: string | Date) {
return new Date(date).toLocaleString('default', {
weekday: 'short',
day: 'numeric',
month: 'long',
})
}
function formatMonth(date: string | Date) {
return new Date(date).toLocaleString('default', {
month: 'short',
@ -170,30 +212,33 @@ const CustomTooltip = ({
payload,
dataKeys,
}: CustomTooltipProps) => {
if (!active) {
if (!active || !payload) {
return null
}
const data = payload[0].payload
return (
<p className="ui__ card">
<small>
{period === 'jours' ? formatDay(data.date) : formatMonth(data.date)}
{period === 'jours' ? formatDayLong(data.date) : formatMonth(data.date)}
</small>
<br />
{dataKeys.map((key: string) => (
<>
<Fragment key={key}>
<strong>{formatValue(data[key])}</strong>{' '}
{dataKeys.length > 1 &&
(key === 'accueil'
? 'visites'
: key === 'simulation_commencee'
? 'simulation commencée'
: key === 'simulation_terminee'
? 'simulation terminée'
: key.replaceAll('_', ' '))}
{dataKeys.length > 1 && formatLegend(key)}
<br />
</>
</Fragment>
))}
</p>
)
}
const formatLegend = (key: string) =>
key === 'accueil'
? 'visites'
: key === 'simulation_commencee'
? 'simulation commencée'
: key === 'simulation_terminee'
? 'simulation terminée'
: key.replaceAll('_', ' ')

View File

@ -30,11 +30,11 @@ export default function SatisfactionChart({ data }: SatisfactionChartProps) {
const flattenData = data.map((d) => ({ ...d, ...toPercentage(d.nombre) }))
return (
<>
<ResponsiveContainer width="100%" height={300}>
<ResponsiveContainer width="100%" height={400}>
<BarChart data={flattenData}>
<XAxis dataKey="date" tickFormatter={formatMonth} />
<Tooltip content={<CustomTooltip />} />
<Bar dataKey="mauvais" stackId="1" fill="#ffcccb" maxBarSize={50}>
<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}>

View File

@ -37,15 +37,17 @@ 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: Array<{
date: string
page_chapter2: string
page_chapter3?: string
page?: string
click?: string
}>,
data: RawData,
chapter2: Chapter2
): Array<{ date: string; nombre: Record<string, number> }> => {
return toPairs(
@ -68,6 +70,25 @@ const filterByChapter2 = (
}))
}
function groupByDate(data: RawData) {
return toPairs(
groupBy(
(p) => p.date,
data.filter((d) => d.page === 'accueil')
)
).map(([date, values]) => ({
date,
nombre: Object.fromEntries(
Object.entries(
groupBy((x) => x.page_chapter1 + ' / ' + x.page_chapter2, values)
)
.map(([k, v]) => [k, v.map((v) => v.nombre).reduce(add, 0)] as const)
.sort((a, b) => b[1] - a[1])
.slice(0, 7)
),
}))
}
const computeTotals = (data: Data): number | Record<string, number> => {
if (typeof data[0].nombre === 'number') {
return (data as Data & { nombre: number }[])
@ -89,6 +110,11 @@ export default function Stats() {
return filterByChapter2(rawData.pages, chapter2)
}, [period, chapter2])
const repartition = useMemo(() => {
const rawData = stats.visitesMois
return groupByDate(rawData.pages)
}, [])
const satisfaction = useMemo(() => {
return filterByChapter2(stats.satisfaction, chapter2)
}, [chapter2])
@ -182,18 +208,14 @@ export default function Stats() {
<h2>Visites</h2>
<Chart
key={period + visites.length}
period={period}
data={visites}
onDateChange={handleDateChange}
startIndex={startDateIndex}
endIndex={endDateIndex}
/>
{period === 'mois' && !!satisfaction.length && (
<>
<h2>Satisfaction</h2>
<SatisfactionChart key={chapter2} data={satisfaction} />
</>
)}
<h2>
Cumuls pour la période{' '}
{period === 'jours'
@ -240,6 +262,24 @@ export default function Stats() {
</>
)}
</Indicators>
{period === 'mois' && !!satisfaction.length && (
<>
<h2>Satisfaction</h2>
<SatisfactionChart key={chapter2} data={satisfaction} />
</>
)}
{chapter2 === '' && period === 'mois' && (
<>
<h2>Principales pages</h2>
<Chart
colored
period={'mois'}
data={repartition}
grid={false}
layout={'vertical'}
/>
</>
)}
</div>
<div
css={`
@ -310,7 +350,7 @@ function getChapter2(s: SimulatorData[keyof SimulatorData]): Chapter2 | '' {
}
function SelectedSimulator(props: { chapter2: Chapter2 }) {
const simulateur = Object.values(useSimulatorsData()).find(
(s) => getChapter2(s) === props.chapter2 && !s.tracking.chapter3
(s) => getChapter2(s) === props.chapter2 && !(s.tracking as any).chapter3
)
if (!simulateur) {
return null

View File

@ -14,6 +14,9 @@ declare namespace NodeJS {
GITHUB_API_SECRET: string
DEEPL_API_SECRET: string
INSEE_SIRENE_API_SECRET: string
ATINTERNET_API_ACCESS_KEY: string
ATINTERNET_API_SECRET_KEY: string
ZAMMAD_API_SECRET_KEY: string
}
interface Global {

View File

@ -4708,11 +4708,16 @@ core-js@^0.8.3:
resolved "https://registry.yarnpkg.com/core-js/-/core-js-0.8.4.tgz#c22665f1e0d1b9c3c5e1b08dabd1f108695e4fcf"
integrity sha1-wiZl8eDRucPF4bCNq9HxCGleT88=
core-js@^2.4.0, core-js@^2.6.10:
core-js@^2.4.0:
version "2.6.11"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c"
integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==
core-js@^2.6.10:
version "2.6.12"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
core-js@^3.2.1:
version "3.6.5"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a"
@ -11180,9 +11185,9 @@ react-signature-pad-wrapper@^1.2.11:
throttle-debounce "^2.1.0"
react-smooth@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/react-smooth/-/react-smooth-1.0.5.tgz#94ae161d7951cdd893ccb7099d031d342cb762ad"
integrity sha512-eW057HT0lFgCKh8ilr0y2JaH2YbNcuEdFpxyg7Gf/qDKk9hqGMyXryZJ8iMGJEuKH0+wxS0ccSsBBB3W8yCn8w==
version "1.0.6"
resolved "https://registry.yarnpkg.com/react-smooth/-/react-smooth-1.0.6.tgz#18b964f123f7bca099e078324338cd8739346d0a"
integrity sha512-B2vL4trGpNSMSOzFiAul9kFAsxTukL9Wyy9EXtkQy3GJr6sZqW9e1nShdVOJ3hRYamPZ94O17r3Q0bjSw3UYtg==
dependencies:
lodash "~4.17.4"
prop-types "^15.6.0"