diff --git a/src/App.tsx b/src/App.tsx index 5653e3f..34b03df 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -19,6 +19,7 @@ import { RepMnuPage } from "./components/pages/rep_mnu"; import { RepDispmedPage } from "./components/pages/rep_dispmed"; import { DmaPageEPCI } from "./components/pages/dma_epci"; import { HomePage } from "./components/pages/home"; +import { ObjectifsPage } from "./components/pages/objectifs"; const myTheme:ThemeConfig = { @@ -86,6 +87,9 @@ const App: React.FC = () => { } /> + + } /> + } /> diff --git a/src/components/chart_gauge_targets/index.tsx b/src/components/chart_gauge_targets/index.tsx index f8b6d09..239b71d 100644 --- a/src/components/chart_gauge_targets/index.tsx +++ b/src/components/chart_gauge_targets/index.tsx @@ -1,6 +1,8 @@ import { GaugeSeriesOption } from 'echarts'; import ReactECharts, { EChartsOption } from 'echarts-for-react'; -import { geekblue as palette , orange} from '@ant-design/colors'; +import { geekblue as palette, red, gold, green, lime, orange} from '@ant-design/colors'; +import { useContext } from 'react'; +import { pageContext } from '../pages/objectifs'; export interface IChartGaugeTargetProps { value:number, @@ -8,12 +10,28 @@ export interface IChartGaugeTargetProps { } export const ChartGaugeTarget: React.FC = ( {value, value_trajectoire} ) => { + const context = useContext(pageContext) - const color = value > value_trajectoire ? palette[5] : orange[5] + const color = ((progress:number) => { + if (progress <= 30) { + return red[3]; + } else if (progress <= 50) { + return orange[3]; + } else if (progress <= 70) { + return gold[3]; + } else if (progress <= 90) { + return lime[3]; + } else if (progress > 90) { + return green[3]; + } else { + return 'grey'; + } + })(value) const myserie:GaugeSeriesOption={ type:"gauge", - center: ['50%', '60%'], + center: ['50%', '100%'], + radius:'160%', startAngle: 180, endAngle: 0 , min: 0, @@ -28,8 +46,14 @@ export const ChartGaugeTarget: React.FC = ( {value, valu show: false }, axisTick:{show:false}, + axisLabel:{show:true, color: '#464646', // Objectif intermédiaire ? + fontSize: 10, + distance: -30, + rotate: 'tangential', + formatter: (v) => {{if(v===75 || v===20){return `2023 \n 75%`} return ``}} + }, + splitNumber:100, splitLine: {show:false}, - axisLabel:{show:false}, axisLine: { lineStyle: { width: 20 @@ -47,15 +71,41 @@ export const ChartGaugeTarget: React.FC = ( {value, valu ...myserie, progress: { show: true, - width: 5 + width: context.remaningTime ? 5 : 0 }, axisLine:{show:false}, + axisLabel:{show:false}, itemStyle:{color:palette[2]}, data:[value_trajectoire],z:99, detail:{show:false} } + + const myserie3:GaugeSeriesOption={ + ...myserie, + progress: { + show:false + }, + pointer: { + show: true, + showAbove:false, + offsetCenter:[0,-50], + icon:"rect", + width:1, + itemStyle:{ + color:"black" + }, + }, + detail: { + show: true, + formatter: '{value} %', + color: 'red', + offsetCenter: [0, '-15%'], fontSize: 18 + }, + data:[75], + + } const option:EChartsOption = { - series:[myserie,myserie2], + series:[myserie], } return( diff --git a/src/components/chart_target_bar/index.tsx b/src/components/chart_target_bar/index.tsx new file mode 100644 index 0000000..11f25ff --- /dev/null +++ b/src/components/chart_target_bar/index.tsx @@ -0,0 +1,149 @@ +import EChartsReact, { EChartsOption } from "echarts-for-react"; + + +export interface IChartTargetBarProps { + data?:any +} + +export const ChartTargetBar: React.FC = ( {data} ) => { + var progressLineLength = 450; + var maxValue = 100; + var value = 70; + + const options:EChartsOption = { + graphic: { + elements: [ + { + type: 'group', + left: 'center', + top: 'center', + children: [ + { + type: 'rect', + shape: { + x:0, + y:20, + width: progressLineLength, + height:10, + }, + style: { + fill: '#E5E5E5' + } + }, + { + type: 'rect', + shape: { + x:0, + y:20, + width: progressLineLength*value/maxValue, + height:10, + }, + style: { + fill: '#3874CB' + } + }, + { + type: 'sector', + style: { + fill: '#E5E5E5' + }, + shape:{ + cx:50, + cy:75, + r:80, + r0:60, + startAngle:Math.PI, + endAngle:Math.PI*2, + clockwise:true + } + }, + { + type: 'sector', + style: { + fill: 'green' + }, + shape:{ + cx:50, + cy:75, + r:80, + r0:60, + startAngle:Math.PI, + endAngle:7*Math.PI/6, //https://i.ytimg.com/vi/OaLuFdRKbAI/maxresdefault.jpg + clockwise:true + } + }, + { + type:"text", + y:20, + x:(progressLineLength*value/maxValue) / 2, + style:{ + text:`${value}%` + } + }, + { + type:"line", + shape:{ + x1:70, + y1:0, + x2:70, + y2:70 + }, + style:{ + lineDash:"dashed" + } + }, + { + type:"text", + y:0, + x:71, + style:{ + text:`2021` + } + }, + { + type:"text", + y:40, + x:71, + style:{ + text:`-15%` + } + }, + + ] + } + ] + } +} + return ( + <> + {/* + {" "} + + +
+
+
+
+
+
*/} + + + + + ); +} \ No newline at end of file diff --git a/src/components/chart_target_evolution/index.tsx b/src/components/chart_target_evolution/index.tsx index 5602480..bebd6c3 100644 --- a/src/components/chart_target_evolution/index.tsx +++ b/src/components/chart_target_evolution/index.tsx @@ -2,26 +2,47 @@ import React from 'react'; import ReactECharts from 'echarts-for-react'; import { EChartsOption, LineSeriesOption } from 'echarts'; import { CSSProperties, useRef } from 'react'; +import alasql from 'alasql'; import { SimpleRecord } from 'g2f-dashboard'; interface IChartTargetEvolutionProps { data: any; + current_year?:number; style? : CSSProperties } -export const ChartTargetEvolution: React.FC = ( {data, style} ) => { +export const ChartTargetEvolution: React.FC = ( {data, style, current_year} ) => { const line_data = data.map((e:SimpleRecord) => ({value:[e.date, e.value]})) const target_data = [ [data[0].ref_date, data[0].ref_value], [data[0].due_date, data[0].target]] - const chartRef = useRef() + // Calcule min et max pour définir les bornes de l'axe Y + const offset_coef = 0.1 + const MinMax = alasql(` SELECT + MIN(MIN([value], [ref_value], [target])) as min, + MAX(MAX([value], [ref_value], [target])) as max + FROM ? + `, [data]).map((e:any) => ({...e, offset:(e.max - e.min)*offset_coef}))[0] + + + const chartRef = useRef(); const serie: LineSeriesOption = { type: "line", - data:line_data, - smooth:true + data: line_data, + smooth: true, + markLine: { + symbol:'none' , + lineStyle:{ + color:'grey', + }, + data: [{ + name: "Current year", + xAxis: current_year?.toString(), + }], + }, }; const serie_target: LineSeriesOption = { @@ -40,6 +61,7 @@ export const ChartTargetEvolution: React.FC = ( {dat yAxis: [ { type: "value", + min:Math.ceil(MinMax.min - MinMax.offset) }, ], }; diff --git a/src/components/pages/objectifs.tsx b/src/components/pages/objectifs.tsx new file mode 100644 index 0000000..58f0baf --- /dev/null +++ b/src/components/pages/objectifs.tsx @@ -0,0 +1,65 @@ +import { + useQuery, + } from "@tanstack/react-query"; +import axios from "axios"; + +import DataJson from "/data/objectifs.json?url"; +import { Card, Col, InputNumber, Row, Switch } from "antd"; +import { TargetCard } from "../target_card"; +import { createContext, useState } from "react"; +import alasql from "alasql"; +import { ChartTargetBar } from "../chart_target_bar"; + +export interface PageContextI { + remaningTime:boolean +} +export const pageContext = createContext({remaningTime:true}); //Context permettant la remontée du ref Echarts enfant + + +export const ObjectifsPage: React.FC = () => { + + const [remaningTime, setRemaningTime] = useState(false) + const [year, setYear] = useState(2021) + + + const {data:cible_indicateur} = useQuery({ + queryKey: ['fdfdsfdsfds'], + queryFn: () => + axios + .get(DataJson) + .then((res) => res.data.sort((a:any,b:any) => Number(a.date) - Number(b.date) )), + }) + + const objectifs = cible_indicateur && alasql(` + SELECT DISTINCT id_cible, cible, indicateur, due_date, ref_date, ref_value, [target], [thématiques] as tags FROM ? + `, [cible_indicateur]) + + return( + + +

Objectifs

+ + Temps restant + e && setYear(e)} /> Année + + + {objectifs?.map((e:any)=> + + + x.id_cible == e.id_cible)} objectif_name={e.indicateur} + tags={e.tags?.split(',')} + date={year.toString()} due_date={e.due_date} ref_date={e.ref_date} + ref_value={e.ref_value} target_value={e.target} /> + + + )} + + + + + + + +
+ ) +} \ No newline at end of file diff --git a/src/components/target_card/index.tsx b/src/components/target_card/index.tsx index 8cc4dac..eb6ecd3 100644 --- a/src/components/target_card/index.tsx +++ b/src/components/target_card/index.tsx @@ -4,44 +4,38 @@ import { Col, Collapse, Descriptions, DescriptionsProps, Row, Tag } from 'antd'; import { ChartTargetEvolution } from '../chart_target_evolution'; import { SimpleRecord } from 'g2f-dashboard'; +const tagColor = (tag: string) => { + switch (tag) { + case "Déchet": + case "Déchets": + return "purple"; + case "Energie": + return "blue"; + case "Climat": + return "orange" + default: + return "grey"; + } +}; + + export interface ITargetCardProps { objectif_name:string, - value:number, unite?:string, date:string, //year ? due_date:string, ref_date:string, ref_value:number, target_value:number, + tags?:string[], data:SimpleRecord[] } -export const TargetCard: React.FC = ( {objectif_name, value, date, due_date, ref_date, ref_value, target_value, unite, data} ) => { - const percent:number = (ref_value - value) / (ref_value - target_value ); +export const TargetCard: React.FC = ( {objectif_name, date, due_date, ref_date, ref_value, target_value, unite, data, tags} ) => { + const value = data.filter((e) => e.date.toString() == date)[0]?.value + const percent:number = value && (ref_value - value) / (ref_value - target_value ); const trajectoire_percent:number = 1 - (Number(due_date) - Number(date)) / ( Number(due_date) - Number(ref_date) ) //Valeur théorique de la trajectoire - const getState = (percent:number, trajectoire_percent:number) => { - if (percent >= 1.1) { - return( Dépassé) - } - else if (percent >= 1) { - return( Atteint) - } - else if (percent < -0.1) { - return( Critique) - } - else if (Math.abs(trajectoire_percent - percent) < 0.05 ) { - return( Sur la trajectoire ) - } - else if (trajectoire_percent - percent > 0.05 ) { - return( En retard ) - } - else if (trajectoire_percent - percent < -0.05 ) { - return( En avance ) - } - } - - const state = getState(percent, trajectoire_percent) //const trajectory_slope = target_value > ref_value ? 'asc' : 'desc' const descriptionItems:DescriptionsProps['items'] = [ @@ -51,7 +45,7 @@ export const TargetCard: React.FC = ( {objectif_name, value, d }, { key: 'value', - label: 'Valeur', children: `${value.toLocaleString()} (${date})` + label: 'Valeur', children: `${value && value.toLocaleString()} (${date})` }, { key: 'target', @@ -61,11 +55,13 @@ export const TargetCard: React.FC = ( {objectif_name, value, d return (
+ {tags?.map((tag) => {tag}) } {objectif_name} - {state} + + @@ -74,7 +70,7 @@ export const TargetCard: React.FC = ( {objectif_name, value, d }]} ghost defaultActiveKey={['1']} /> + children: }]} ghost defaultActiveKey={['1']} />
) }