diff --git a/src/lib/components/impacts_view/test_cases_view/test_case_selected/PaySlipViewOld.svelte b/src/lib/components/impacts_view/test_cases_view/test_case_selected/PaySlipViewOld.svelte new file mode 100644 index 0000000000000000000000000000000000000000..067133a29d5f2432ebf74a5ebc755e1fd8c7caa2 --- /dev/null +++ b/src/lib/components/impacts_view/test_cases_view/test_case_selected/PaySlipViewOld.svelte @@ -0,0 +1,705 @@ +<script lang="ts"> + import type { VariableByName } from "@openfisca/json-model" + + import { goto } from "$app/navigation" + import OilSpendingBill from "$lib/components/impacts_view/test_cases_view/test_case_selected/OilSpendingBill.svelte" + import TestCaseGraph from "$lib/components/impacts_view/test_cases_view/test_case_selected/graph/TestCaseGraph.svelte" + import Tooltip from "$lib/components/ui_transverse_components/Tooltip.svelte" + import { + type VisibleDecomposition, + type EvaluationByName, + isChildOrDescendant, + } from "$lib/decompositions" + import { buildVisibleDecompositions } from "$lib/decompositions" + import type { DisplayMode } from "$lib/displays" + import { entityByKey, personEntityKey } from "$lib/entities" + import { trackTestCaseGraph } from "$lib/matomo" + import { revaluationName, shared } from "$lib/shared.svelte" + import { + type ActiveSlider, + getSituationVariableValue, + setSituationVariableValue, + type Situation, + } from "$lib/situations" + import { newSimulationUrl } from "$lib/urls" + import { removeNegativeZero } from "$lib/values" + import { + oilTypes, + type ValuesByCalculationNameByVariableName, + type VariableValue, + } from "$lib/variables" + import LinkedVariables from "$lib/components/impacts_view/test_cases_view/test_case_selected/LinkedVariables.svelte" + import { iterToDepth } from "$lib/iterators" + import StandardOfLiving from "$lib/components/impacts_view/test_cases_view/test_case_selected/StandardOfLiving.svelte" + + interface Props { + displayMode: DisplayMode + evaluationByName: EvaluationByName + showLoader?: boolean + situation: Situation + situationIndex: number + valuesByCalculationNameByVariableName: ValuesByCalculationNameByVariableName + variableSummaryByName: VariableByName + year: number + } + + let { + displayMode, + evaluationByName, + showLoader = true, + situation = $bindable(), + situationIndex, + valuesByCalculationNameByVariableName, + variableSummaryByName, + year, + }: Props = $props() + + const dateFormatter = new Intl.DateTimeFormat("fr-FR", { dateStyle: "full" }) + .format + const deltaFormatter = (value: number): string => + new Intl.NumberFormat("fr-FR", { + currency: "EUR", + maximumFractionDigits: 0, + minimumFractionDigits: 0, + signDisplay: "never", + style: "currency", + }).format(removeNegativeZero(value)) + const firstDeltaFormatter = (value: number): string => + new Intl.NumberFormat("fr-FR", { + currency: "EUR", + maximumFractionDigits: 0, + minimumFractionDigits: 0, + style: "currency", + }).format(removeNegativeZero(value)) + const personEntity = entityByKey[personEntityKey] + + const oilSpendings = oilTypes.map((name) => ({ + depenseTtcVariableName: `depense_${name}_ttc`, + nombreLitresVariableName: `nombre_litres_${name}`, + // prixTtcLitreVariableName: `prix_${name}_hors_remise_ttc_sortie`, + prixTtcLitreVariableName: `prix_${name}_ttc`, + ticpeVariableName: `${name}_ticpe`, + tvaVariableName: `tva_sur_${name}`, + })) + + let useRevaluationInsteadOfLaw = $derived(revaluationName !== undefined) + + let firstCalculationName = $derived( + useRevaluationInsteadOfLaw ? "revaluation" : "law", + ) + + let runningCalculationNames = $derived( + Object.entries(shared.calculationByName) + .filter(([, calculation]) => calculation.running) + .map(([calculationName]) => calculationName), + ) + + let modificationsAmendmentCount = $derived( + Object.keys(shared.parametricReform).length, + ) + + let visibleDecompositions: VisibleDecomposition[] = $state([]) + + $effect(() => { + visibleDecompositions = buildVisibleDecompositions( + shared.decompositionByName, + entityByKey, + evaluationByName, + situation, + variableSummaryByName, + shared.waterfall, + shared.showNulls, + useRevaluationInsteadOfLaw, + shared.vectorLength, + year, + ) + }) + + let personSituation = $derived(situation[personEntity.key_plural!]) + + function getCorrectSimulationUrl(variableName: string) { + const newDisplayMode = + displayMode.edit !== undefined + ? { + ...displayMode, + variableName: variableName, + } + : { + ...displayMode, + mobileLaw: true, + parametersVariableName: variableName, + } + return newSimulationUrl(newDisplayMode) + } + + function getFirstPersonActivity(personSituation) { + const populationId = Object.keys(personSituation).sort( + (populationId1, populationId2) => + populationId1.localeCompare(populationId2), + )[0] + return getVariableValue(situation, "activite", populationId) + } + + function getVariableValue( + situation: Situation, + variableName: string, + populationId: string, + ): VariableValue | undefined { + const variable = variableSummaryByName[variableName] + if (variable === undefined) { + return undefined + } + return getSituationVariableValue(situation, variable, populationId, year) + } + + function removeSituationSlider() { + if (shared.savedSituation !== undefined) { + situation = shared.savedSituation + delete shared.savedSituation + if (shared.savedSituationIndex !== undefined) { + shared.testCases[shared.savedSituationIndex] = situation + delete shared.savedSituationIndex + } + } + } + + function requestAxesCalculation() { + // Ensure that situation of previous curve is not merged with the new one. + if ( + shared.savedSituation !== undefined && + shared.savedSituationIndex !== undefined + ) { + shared.testCases[shared.savedSituationIndex] = shared.savedSituation + } + + shared.savedSituation = situation + shared.savedSituationIndex = situationIndex + situation = structuredClone($state.snapshot(situation)) + shared.testCases[situationIndex] = situation + if ( + situation.sliders?.length === undefined || + situation.sliders?.length <= 0 + ) { + console.error( + "requestAxesCalculation", + "Situation sliders list is undefined", + ) + return + } + + // Get situation + const variable = variableSummaryByName[situation.sliders?.[0].name] + const slider = situation.sliders.find( + (slider) => + slider.entity === variable.entity && slider.name === variable.name, + ) as ActiveSlider | undefined + + if (slider === undefined) { + console.error("requestAxesCalculation", "Slider is undefined") + return + } + + const updatedMin = slider.min + const updatedMax = slider.max + const updatedStepValue = (updatedMax - updatedMin) / 100 ?? slider.stepValue + + const value = getSituationVariableValue( + situation, + variable, + slider.id, + year, + ) as number + + const vectorIndex = Math.max( + 0, + Math.min(100, Math.round(value / updatedStepValue)), + ) + + // Update situation + setSituationVariableValue( + entityByKey, + situation, + variable, + slider.id, + year, + Math.round(updatedStepValue * vectorIndex), + ) + situation.slider = { + ...slider, + min: updatedMin, + max: updatedMax, + stepValue: updatedStepValue, + vectorIndex: vectorIndex, + } + } + + function zoomIn(index: number) { + let visibleDecomposition = visibleDecompositions[index] + if ( + visibleDecomposition === undefined || + visibleDecomposition.visibleChildren === undefined + ) { + return + } + let decomposition = visibleDecomposition.decomposition + if (!decomposition.open) { + shared.decompositionByName[decomposition.name] = { + ...decomposition, + open: true, + } + return + } + } + + function zoomOut(index: number) { + let visibleDecomposition = visibleDecompositions[index] + if ( + visibleDecomposition === undefined || + visibleDecomposition.visibleChildren === undefined + ) { + return + } + let decomposition = visibleDecomposition.decomposition + if (decomposition.open) { + shared.decompositionByName[decomposition.name] = { + ...decomposition, + open: false, + } + return + } + } +</script> + +<!--Ce composant se découpe en 3 zones : +- ZONE 1 : FEUILLE DE PAIE +- ZONE 2 : VARIALBES COMPLÉMENTAIRES À LA FEUILLE DE PAIE +- ZONE 3 : TICKET DE CARBURANT & GRAPHIQUE DES CAS TYPES +--> + +{#if visibleDecompositions.length > 0} + {@const firstPersonActivity = getFirstPersonActivity(personSituation)} + + <!--ZONE 1 : FEUILLE DE PAIE --> + <!-- Bouton affichage des montants nuls --> + <div + class="mx-4 mb-3 flex justify-start" + id="situation_{situationIndex}_waterfall_showall" + > + <label class="inline-flex cursor-pointer items-center"> + <input + type="checkbox" + value="" + class="peer sr-only" + bind:checked={shared.showNulls} + /> + <div + class="peer relative h-6 w-11 shrink-0 rounded-full bg-gray-400 after:absolute after:start-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:bg-white after:transition-all after:content-[''] peer-checked:bg-le-bleu peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-0" + ></div> + <span class="ms-3 text-sm font-medium text-gray-900 dark:text-gray-300" + >Montrer tous les dispositifs<span + class="hidden sm:inline-flex md:hidden lg:inline-flex" + >, y compris si leur montant est à 0€</span + ></span + > + </label> + </div> + <!-- Feuille de paie--> + <div class="flex justify-between"> + <div class="w-3/5 flex-auto"> + {#each visibleDecompositions as { decomposition, depth, rows, trunk, visibleChildren }, index} + <!-- Ligne de la feuille de paie--> + <div + class="flex min-h-8 items-stretch justify-between border-t border-gray-200 px-4" + class:bg-gray-100={trunk && index !== 0} + class:border-gray-300={trunk && index !== 0} + class:fond={decomposition.name === + displayMode.parametersVariableName || + (displayMode.parametersVariableName && + isChildOrDescendant( + shared.decompositionByName, + decomposition.name, + displayMode.parametersVariableName, + ))} + class:text-lg={decomposition.name === + displayMode.parametersVariableName} + class:items-start={decomposition.name === + displayMode.parametersVariableName} + > + <!-- Colonne 1 "Nom du dispositif"--> + <div class="flex-1 overflow-x-hidden"> + {#each rows as { calculationName }} + {#if calculationName === firstCalculationName} + <div class="flex h-full w-full items-center"> + <!--Si c'est une variable de type tronc, exemple : Rémunération brute --> + {#if trunk && index !== 0} + <!--Les class permettent que le nom de la variable soit caché avec "..." et rendu visible au survol. Le comportement est différent si la variable est sélectionnée--> + <div + class="flex w-full items-center" + class:whitespace-nowrap={decomposition.name !== + displayMode.parametersVariableName} + > + <!-- Nom de la variable tronc, cliquable --> + <a + class="cursor-pointer overflow-x-hidden text-ellipsis text-gray-500 hover:z-20 hover:overflow-x-visible hover:bg-white hover:bg-opacity-90 hover:pr-1 hover:text-le-gris-dispositif-dark hover:underline" + class:font-bold={decomposition.name === + displayMode.parametersVariableName} + class:hover:absolute={decomposition.name !== + displayMode.parametersVariableName} + href={getCorrectSimulationUrl(decomposition.name)} + data-sveltekit-noscroll + >{decomposition.short_label ?? decomposition.label}</a + > + </div> + {:else if visibleChildren === undefined} + <!--Si c'est une variable Non-trunk, leaf-variable : La variable n'a pas de variables enfants et elle n'est pas une variable tronc --> + + <div + class="flex h-full w-full items-center" + class:whitespace-nowrap={decomposition.name !== + displayMode.parametersVariableName} + > + <!--Indentation pour chaque niveau de l'arbre, illustré par une bordure--> + {#each iterToDepth(depth)} + <div + class={`min-h-full border-l-2 bg-white pr-3 ${ + decomposition.name !== + displayMode.parametersVariableName && + !( + displayMode.parametersVariableName && + isChildOrDescendant( + shared.decompositionByName, + decomposition.name, + displayMode.parametersVariableName, + ) + ) + ? "border-gray-400" + : "border-black" + }`} + ></div> + {/each} + <!--Si la variable est obsolète, ou que sa date de relecture est inconnue ou trop ancienne, un picto attention est affiché --> + {#if decomposition.obsolete || decomposition.last_value_still_valid_on === undefined || decomposition.last_value_still_valid_on < (new Date().getFullYear() - 2).toString()} + <Tooltip + allowFlip={false} + arrowClass="bg-gray-100" + widthClass="w-80" + initialPlacement="bottom" + > + <iconify-icon + class="mr-0.5 shadow-none {decomposition.obsolete + ? 'text-[#FF4133]' + : 'text-[#FFAC33]'}" + icon="material-symbols:warning-rounded" + width="16" + height="16" + ></iconify-icon> + {#snippet tooltip()} + <div + class="overflow-hidden rounded-lg border border-gray-200 bg-white shadow-2xl" + > + <div + class="border-b border-gray-200 bg-gray-100 px-3 py-2" + > + <h3 class="font-semibold text-gray-900"> + âš ï¸ Ce dispositif n'est peut-être pas à jour + </h3> + </div> + <div + class="px-3 py-2 text-sm font-light text-gray-500" + > + Dernière relecture : + {#if decomposition.last_value_still_valid_on === undefined} + date indéterminée + {:else}{dateFormatter( + new Date( + decomposition.last_value_still_valid_on, + ), + )} + {/if} + {#if decomposition.obsolete} + ; Obsolète !{/if} + </div> + </div> + {/snippet} + </Tooltip> + {/if} + <!-- Nom de la variable non-trunk, leaf, cliquable --> + <a + class="cursor-pointer overflow-x-hidden text-ellipsis font-serif hover:z-20 hover:overflow-x-visible hover:bg-white hover:bg-opacity-90 hover:pr-1 hover:text-le-gris-dispositif-dark hover:underline" + class:font-bold={decomposition.name === + displayMode.parametersVariableName} + class:hover:text-wrap={decomposition.name !== + displayMode.parametersVariableName} + href={getCorrectSimulationUrl(decomposition.name)} + data-sveltekit-noscroll + >{decomposition.short_label ?? decomposition.label}</a + > + </div> + {:else} + <!-- Si c'est une variable Non-trunk, Non-leaf : La variable a des variables enfants et n'est pas une variable tronc | une flèche à droite permet d'ouvrir les variables enfants--> + + <div + class="flex h-full w-full items-center justify-start" + class:whitespace-nowrap={decomposition.name !== + displayMode.parametersVariableName} + > + <!--Indentation pour chaque niveau de l'arbre, illustré par une bordure--> + {#each iterToDepth(depth)} + <div + class={`min-h-full border-l-2 bg-white pr-3 ${ + decomposition.name !== + displayMode.parametersVariableName && + !( + displayMode.parametersVariableName && + isChildOrDescendant( + shared.decompositionByName, + decomposition.name, + displayMode.parametersVariableName, + ) + ) + ? "border-gray-400" + : "border-black" + }`} + ></div> + {/each} + <button + class="cursor-pointer overflow-x-hidden text-ellipsis text-left font-serif hover:z-20 hover:overflow-x-visible hover:bg-white hover:bg-opacity-90 hover:text-le-gris-dispositif-dark hover:underline" + class:hover:text-wrap={decomposition.name !== + displayMode.parametersVariableName} + class:font-bold={decomposition.name === + displayMode.parametersVariableName} + onclick={() => { + // Non-leaf decomposition node in variable inputs mode => no-link + if (decomposition.open) { + zoomOut(index) + } else { + zoomIn(index) + } + // Leaf decomposition node with parameters in parameters mode => link + if (displayMode.edit === undefined) { + goto(getCorrectSimulationUrl(decomposition.name), { + noScroll: true, + }) + } + }} + data-sveltekit-noscroll + >{decomposition.short_label ?? + decomposition.label}</button + > + <button + class="text-black" + aria-label={decomposition.open + ? "Ouvrir variables enfants" + : "Fermer"} + onclick={() => + decomposition.open ? zoomOut(index) : zoomIn(index)} + > + <iconify-icon + class="align-[-0.25rem] text-lg hover:text-le-gris-dispositif" + icon={decomposition.open + ? "ri-arrow-down-s-line" + : "ri-arrow-right-s-line"} + ></iconify-icon> + </button> + </div> + {/if} + </div> + {/if} + {/each} + </div> + + <!-- Colonne 2 - Valeur du dispositif--> + <div + class="flex flex-nowrap pr-1 sm:items-center md:pr-4" + class:py-1={!trunk} + class:items-end={!trunk} + > + <div class="flex flex-col justify-end sm:flex-row sm:items-center"> + {#each rows as { calculationName, deltaAtVectorIndex }} + <span + class="h-full pl-1 text-right text-sm" + class:content-center={trunk} + class:content-start={!trunk} + > + <!--Loader squelette pendant que la valeur charge --> + {#if showLoader && runningCalculationNames.length > 0} + {#if runningCalculationNames.includes("law") || runningCalculationNames.includes("revaluation")} + <span + class="animate-pulse-2 bg-gray-500 px-1 text-black blur-xs" + > + <span class="text-white blur">value €</span> + </span> + {/if} + {#if runningCalculationNames.includes("bill")} + <span + class="animate-pulse-2 bg-le-rouge-bill px-1 text-black blur-xs" + > + <span class="text-white blur">value €</span> + </span> + {/if} + {#if runningCalculationNames.includes("amendment") && modificationsAmendmentCount > 0} + <span + class="animate-pulse-2 bg-le-jaune px-1 text-black blur-xs" + > + <span class="text-white blur">value €</span> + </span> + {/if} + <!--Valeur du waterfall--> + {:else if trunk && index !== 0} + <span + class=" {calculationName === firstCalculationName + ? rows.find((row) => row.calculationName === 'bill') === + undefined + ? rows.find( + (row) => row.calculationName === 'amendment', + ) === undefined + ? 'text-gray-500' + : 'text-gray-500 line-through-amendment' + : 'text-gray-500 line-through-bill' + : calculationName === 'bill' + ? rows.find( + (row) => row.calculationName === 'amendment', + ) === undefined + ? 'text-le-rouge-bill' + : 'text-le-rouge-bill line-through-amendment' + : 'bg-le-jaune text-gray-500'}" + class:text-base={decomposition.name !== + displayMode.parametersVariableName} + class:text-2xl={decomposition.name === + displayMode.parametersVariableName} + class:font-bold={decomposition.name === + displayMode.parametersVariableName} + ><span + class="content-center pr-1 font-normal text-gray-500" + >=</span + >{firstDeltaFormatter(deltaAtVectorIndex ?? 0)}</span + > + {:else} + <span + class="font-bold {calculationName === firstCalculationName + ? rows.find((row) => row.calculationName === 'bill') === + undefined + ? rows.find( + (row) => row.calculationName === 'amendment', + ) === undefined + ? '' + : 'line-through-amendment' + : 'line-through-bill' + : calculationName === 'bill' + ? rows.find( + (row) => row.calculationName === 'amendment', + ) === undefined + ? 'text-le-rouge-bill' + : 'text-le-rouge-bill line-through-amendment' + : 'bg-le-jaune'}" + class:opacity-20={decomposition.open} + class:text-base={decomposition.name !== + displayMode.parametersVariableName} + class:text-3xl={decomposition.name === + displayMode.parametersVariableName} + class:font-semibold={decomposition.name === + displayMode.parametersVariableName} + >{#if index !== 0}{#if deltaAtVectorIndex < 0}-{:else if deltaAtVectorIndex > 0}+{/if}{/if} + {deltaFormatter(deltaAtVectorIndex ?? 0)}</span + > + {/if} + </span> + {/each} + </div> + </div> + </div> + <!--Affichage des variables liées s'il y en a--> + {#if displayMode.parametersVariableName !== undefined && decomposition.name === displayMode.parametersVariableName} + <LinkedVariables + {displayMode} + {evaluationByName} + {situationIndex} + {variableSummaryByName} + {decomposition} + {depth} + {visibleChildren} + /> + {/if} + {/each} + </div> + </div> + + <!--AJout de la variable niveau de vie--> + {#if shared.waterfall.name === "brut_to_disponible"} + <StandardOfLiving {situation} {valuesByCalculationNameByVariableName} /> + {/if} + + <!--ZONE 3.A : TICKET DE CARBURANT--> + {#if shared.waterfall.name === "disponible_to_disponible_apres_taxes_carburant"} + {#each oilSpendings as { depenseTtcVariableName, nombreLitresVariableName, prixTtcLitreVariableName, ticpeVariableName, tvaVariableName }} + <OilSpendingBill + {depenseTtcVariableName} + {nombreLitresVariableName} + {prixTtcLitreVariableName} + on:changeTestCaseToEditIndex + {situation} + {situationIndex} + {ticpeVariableName} + {tvaVariableName} + {valuesByCalculationNameByVariableName} + {year} + /> + {/each} + {:else} + <!--ZONE 3.B : GRAPHIQUE DES CAS TYPES--> + {#if firstPersonActivity !== "inactif"} + <button + class="mt-10 flex w-full items-center gap-2 border-b px-4 py-2 text-start text-neutral-600 transition-all hover:bg-neutral-100 active:bg-neutral-200" + onclick={() => { + if (situation.slider === undefined) { + requestAxesCalculation() + trackTestCaseGraph(firstPersonActivity) + } else { + removeSituationSlider() + } + }} + > + <iconify-icon class="text-lg" icon="ri-line-chart-fill"></iconify-icon> + <span class="flex-1 font-bold"> + Voir ce cas type sur un graphique faisant évoluer {#if firstPersonActivity === "actif"} + le salaire + {:else if firstPersonActivity === "retraite"} + la retraite + {:else if firstPersonActivity === "chomeur"} + le chômage + {:else} + le revenu + {/if} + : + </span> + <iconify-icon + class="text-3xl" + icon={situation.slider !== undefined + ? "ri-arrow-drop-down-line" + : "ri-arrow-drop-right-line"} + ></iconify-icon> + </button> + <TestCaseGraph + {displayMode} + {evaluationByName} + evaluationByNameArray={shared.evaluationByNameArray} + {situation} + {situationIndex} + {useRevaluationInsteadOfLaw} + {valuesByCalculationNameByVariableName} + {variableSummaryByName} + vectorLength={shared.vectorLength} + waterfall={shared.waterfall} + {year} + /> + {/if} + {/if} +{/if} + +<style lang="postcss"> + .fond { + background-color: #ffffff; + /* Polka dots - Heropatterns.com échelle réduite */ + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3E%3Cpath fill='%23A0A0A0' fill-opacity='0.4' d='M1 3h1v1H1V3zm2-2h1v1H3V1z'%3E%3C/path%3E%3C/svg%3E"); + } +</style> diff --git a/src/lib/components/impacts_view/test_cases_view/test_case_selected/compare_mode/PaySlipCompareViewOld.svelte b/src/lib/components/impacts_view/test_cases_view/test_case_selected/compare_mode/PaySlipCompareViewOld.svelte new file mode 100644 index 0000000000000000000000000000000000000000..9243424b99151b199b071e9a85e909098d3244df --- /dev/null +++ b/src/lib/components/impacts_view/test_cases_view/test_case_selected/compare_mode/PaySlipCompareViewOld.svelte @@ -0,0 +1,588 @@ +<script lang="ts"> + import { preventDefault } from "svelte/legacy" + + import type { VariableByName } from "@openfisca/json-model" + + import { goto } from "$app/navigation" + import { page } from "$app/stores" + import Tooltip from "$lib/components/ui_transverse_components/Tooltip.svelte" + import type { + EvaluationByName, + VisibleDecompositionForComparison, + } from "$lib/decompositions" + import { buildVisibleDecompositionsForComparison } from "$lib/decompositions" + import type { DisplayMode } from "$lib/displays" + import { entityByKey } from "$lib/entities" + import { shared } from "$lib/shared.svelte" + import type { Situation } from "$lib/situations" + import { newSimulationUrl } from "$lib/urls" + import { removeNegativeZero } from "$lib/values" + + interface Props { + displayMode: DisplayMode + evaluationByNameArray: EvaluationByName[] + situations: Situation[] + situationsToCompareIndex: number[] + showLoader?: boolean + variableSummaryByName: VariableByName + year: number + } + + let { + displayMode, + evaluationByNameArray, + situations, + situationsToCompareIndex, + showLoader = true, + variableSummaryByName, + year, + }: Props = $props() + + const dateFormatter = new Intl.DateTimeFormat("fr-FR", { dateStyle: "full" }) + .format + const deltaFormatter = (value: number): string => + new Intl.NumberFormat("fr-FR", { + currency: "EUR", + maximumFractionDigits: 0, + minimumFractionDigits: 0, + signDisplay: "never", + style: "currency", + }).format(removeNegativeZero(value)) + + let useRevaluationInsteadOfLaw = $derived( + $page.data.revaluationName !== undefined, + ) + + let firstCalculationName = $derived( + useRevaluationInsteadOfLaw ? "revaluation" : "law", + ) + + let runningCalculationNames = $derived( + Object.entries(shared.calculationByName) + .filter(([, calculation]) => calculation.running) + .map(([calculationName]) => calculationName), + ) + + let modificationsAmendmentCount = $derived( + Object.keys(shared.parametricReform).length, + ) + + let visibleDecompositions: VisibleDecompositionForComparison[] = $state([]) + + $effect(() => { + visibleDecompositions = buildVisibleDecompositionsForComparison( + shared.decompositionByName, + entityByKey, + situationsToCompareIndex.map( + (situationIndex) => evaluationByNameArray[situationIndex], + ), + situationsToCompareIndex.map( + (situationIndex) => situations[situationIndex], + ), + variableSummaryByName, + shared.waterfall, + shared.showNulls, + useRevaluationInsteadOfLaw, + shared.vectorLength, + year, + ) + }) + + function* iterToDepth(depth: number): Generator<number, void, unknown> { + for (let i = 0; i < depth; i++) { + yield i + } + } + + function zoomIn(index: number) { + let visibleDecomposition = visibleDecompositions[index] + if ( + visibleDecomposition === undefined || + visibleDecomposition.visibleChildren === undefined + ) { + return + } + let decomposition = visibleDecomposition.decomposition + if (!decomposition.open) { + shared.decompositionByName[decomposition.name] = { + ...decomposition, + open: true, + } + return + } + } + + function zoomOut(index: number) { + let visibleDecomposition = visibleDecompositions[index] + if ( + visibleDecomposition === undefined || + visibleDecomposition.visibleChildren === undefined + ) { + return + } + let decomposition = visibleDecomposition.decomposition + if (decomposition.open) { + shared.decompositionByName[decomposition.name] = { + ...decomposition, + open: false, + } + return + } + } +</script> + +{#if visibleDecompositions.length > 0} + <div> + <div class="h-5 bg-gradient-to-b from-gray-100 to-transparent" /> + <!--Montants-nuls--> + <div class="mx-1 mb-3 flex justify-start"> + <label class="inline-flex cursor-pointer items-center"> + <input + type="checkbox" + value="" + class="peer sr-only" + bind:checked={shared.showNulls} + /> + <div + class="peer relative h-6 w-11 shrink-0 rounded-full bg-gray-400 after:absolute after:start-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:bg-white after:transition-all after:content-[''] peer-checked:bg-le-bleu peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-0" + ></div> + <span class="ms-3 text-sm font-medium text-gray-900 dark:text-gray-300" + >Montrer tous les dispositifs, y compris si leur montant est à 0€.</span + > + </label> + </div> + </div> + <!-- Labels and compared amounts of decompositions --> + <div> + {#each visibleDecompositions as { decomposition, depth, rows, trunk, visibleChildren }, index} + <!-- Decomposition label --> + <div class="flex h-7 items-center whitespace-nowrap"> + {#if trunk} + <div + class="ml-2 mt-2 w-full overflow-x-hidden text-ellipsis border-gray-300 text-left text-base text-gray-500 hover:z-20 hover:overflow-x-visible" + class:border-t={index !== 0} + > + {#if displayMode.edit !== undefined} + <!-- Trunk decomposition node, in variable inputs mode => link --> + <a + class="cursor-pointer text-base hover:underline" + href={newSimulationUrl({ + ...displayMode, + variableName: decomposition.name, + })} + data-sveltekit-noscroll + >{decomposition.short_label ?? decomposition.label}</a + > + {:else} + <!-- Trunk decomposition node with parameters, in parameters mode => link --> + <a + class="cursor-pointer text-base hover:underline" + href={newSimulationUrl({ + ...displayMode, + mobileLaw: true, + parametersVariableName: decomposition.name, + })} + data-sveltekit-noscroll + >{decomposition.short_label ?? decomposition.label}</a + > + {/if} + </div> + {:else} + {#each [...iterToDepth(depth)] as _level} + <div class="ml-2 h-full border-l border-le-gris-dispositif"> + + </div> + {/each} + {#if visibleChildren === undefined} + <!-- Leaf node (except the first one, that belongs to trunk) --> + <div class="ml-4"> + {#if decomposition.obsolete || decomposition.last_value_still_valid_on === undefined || decomposition.last_value_still_valid_on < (new Date().getFullYear() - 2).toString()} + <Tooltip + allowFlip={false} + arrowClass="bg-gray-100" + widthClass="w-80" + initialPlacement="bottom" + > + <iconify-icon + class="mr-1 shadow-none {decomposition.obsolete + ? 'text-[#FF4133]' + : 'text-[#FFAC33]'}" + icon="material-symbols:warning-rounded" + width="16" + height="16" + ></iconify-icon> + {#snippet tooltip()} + <div + class="overflow-hidden rounded-lg border border-gray-200 bg-white shadow-2xl" + > + <div + class="border-b border-gray-200 bg-gray-100 px-3 py-2" + > + <h3 class="font-semibold text-gray-900"> + âš ï¸ Ce dispositif n'est peut-être pas à jour + </h3> + </div> + <div class="px-3 py-2 text-sm font-light text-gray-500"> + Dernière relecture : + {#if decomposition.last_value_still_valid_on === undefined} + date indéterminée + {:else}{dateFormatter( + new Date(decomposition.last_value_still_valid_on), + )} + {/if} + {#if decomposition.obsolete} + ; Obsolète !{/if} + </div> + </div> + {/snippet} + </Tooltip> + {/if} + </div> + + {#if displayMode.edit !== undefined} + <!-- Leaf decomposition node in variable inputs mode => link --> + <a + class="cursor-pointer overflow-x-hidden text-ellipsis font-serif text-base hover:z-20 hover:overflow-x-visible hover:bg-white hover:text-le-gris-dispositif hover:underline" + href={newSimulationUrl({ + ...displayMode, + variableName: decomposition.name, + })} + data-sveltekit-noscroll + >{decomposition.short_label ?? decomposition.label}</a + > + {:else} + <!-- Leaf decomposition node with parameters in parameters mode => link --> + <a + class="cursor-pointer overflow-x-hidden text-ellipsis font-serif text-base hover:z-20 hover:overflow-x-visible hover:bg-white hover:text-le-gris-dispositif hover:underline" + href={newSimulationUrl({ + ...displayMode, + mobileLaw: true, + parametersVariableName: decomposition.name, + })} + data-sveltekit-noscroll + >{decomposition.short_label ?? decomposition.label}</a + > + {/if} + {:else} + <!-- Non-trunk, non-leaf variablev--> + {#if decomposition.open} + <button class="p-0 text-black" onclick={() => zoomOut(index)}> + <iconify-icon + class="align-[-0.2rem] text-lg" + icon="ri-arrow-up-s-line" + ></iconify-icon> + </button> + {:else} + <button class="p-0 text-black" onclick={() => zoomIn(index)}> + <iconify-icon + class="align-[-0.2rem] text-lg" + icon="ri-arrow-right-s-line" + ></iconify-icon> + </button> + {/if} + + {#if displayMode.edit !== undefined} + <!-- Non-lead decomposition node in variable inputs mode => no-link --> + <button + class="cursor-pointer overflow-x-hidden text-ellipsis font-serif text-base text-black hover:z-20 hover:overflow-x-visible hover:bg-white hover:text-black hover:underline" + onclick={() => { + if (decomposition.open) { + zoomOut(index) + } else { + zoomIn(index) + } + }} + > + {decomposition.short_label ?? decomposition.label} + </button> + {:else} + <!-- Leaf decomposition node with parameters in parameters mode => link --> + <a + class="cursor-pointer overflow-x-hidden text-ellipsis font-serif text-base hover:z-20 hover:overflow-x-visible hover:bg-white hover:text-le-gris-dispositif hover:underline" + href={newSimulationUrl({ + ...displayMode, + parametersVariableName: decomposition.name, + })} + onclick={preventDefault(() => { + if (decomposition.open) { + zoomOut(index) + } else { + zoomIn(index) + } + goto( + newSimulationUrl({ + ...displayMode, + parametersVariableName: decomposition.name, + }), + { noScroll: true }, + ) + })} + data-sveltekit-noscroll + >{decomposition.short_label ?? decomposition.label}</a + > + {/if} + {/if} + {/if} + </div> + + {#each rows as { calculationName, deltaAtVectorIndexArray }} + <!-- Decomposition compared amounts --> + {#if !decomposition.open || trunk || index === 0} + <div class="relative flex items-center whitespace-nowrap"> + {#if !decomposition.open && !trunk} + <div class="absolute"> + {#each iterToDepth(depth)} + <div class="ml-2 h-full border-l border-le-gris-dispositif"> + + </div> + {/each} + </div> + {/if} + <div + class="justify-center {decomposition.open && + trunk && + visibleChildren.length > 1 && + calculationName === firstCalculationName + ? '' + : ' border-none'} {calculationName === firstCalculationName + ? '' + : '-mt-1 mb-1'} flex w-full text-sm" + > + {#if decomposition.open || index === 0} + <!---Composant loader pour les valeurs intermédiaires--> + {#if showLoader && runningCalculationNames.length > 0} + {#if runningCalculationNames.includes("law") || runningCalculationNames.includes("revaluation")}<span + class="mx-1 animate-pulse-2 bg-gray-500 px-1 text-black blur-xs" + > + <span class="text-white blur">value €</span> + </span> + {/if} + {#if runningCalculationNames.includes("bill")} + <span + class="mx-1 animate-pulse-2 bg-le-rouge-bill px-1 text-black blur-xs" + > + <span class="text-white blur">value €</span> + </span> + {/if} + {#if runningCalculationNames.includes("amendment") && modificationsAmendmentCount > 0} + <span + class="mx-1 animate-pulse-2 bg-le-jaune px-1 text-black blur-xs" + > + <span class="text-white blur">value €</span> + </span> + {/if} + <!--Valeurs du waterfall des montants intermédiaires --> + {:else if trunk || index === 0} + {#if deltaAtVectorIndexArray[0] === deltaAtVectorIndexArray[1]} + {#if decomposition.open && trunk} + <span class="mx-2 text-gray-500">=</span> + {/if} + <span + class="text-sm {calculationName === firstCalculationName + ? rows.find((row) => row.calculationName === 'bill') === + undefined + ? rows.find( + (row) => row.calculationName === 'amendment', + ) === undefined + ? 'text-gray-500' + : 'text-gray-500 line-through-amendment' + : 'text-gray-500 line-through-bill' + : calculationName === 'bill' + ? rows.find( + (row) => row.calculationName === 'amendment', + ) === undefined + ? 'text-le-rouge-bill' + : 'text-le-rouge-bill line-through-amendment' + : 'bg-le-jaune'}" + >{deltaFormatter(deltaAtVectorIndexArray[0] ?? 0)}</span + > + {:else} + <div class="flex w-full justify-center"> + <div class="mx-4 w-1/2"> + {#if decomposition.open && trunk} + <span class="mx-2 text-gray-500">=</span> + {/if} + <span + class="text-right text-sm {calculationName === + firstCalculationName + ? rows.find( + (row) => row.calculationName === 'bill', + ) === undefined + ? rows.find( + (row) => row.calculationName === 'amendment', + ) === undefined + ? 'text-gray-500' + : 'text-gray-500 line-through-amendment' + : 'text-gray-500 line-through-bill' + : calculationName === 'bill' + ? rows.find( + (row) => row.calculationName === 'amendment', + ) === undefined + ? 'text-le-rouge-bill' + : 'text-le-rouge-bill line-through-amendment' + : 'bg-le-jaune'}" + >{deltaFormatter( + deltaAtVectorIndexArray[0] ?? 0, + )}</span + > + </div> + <div class="mx-4 w-1/2"> + {#if decomposition.open && trunk} + <span class="mx-2 text-gray-500">=</span> + {/if} + <span + class="text-left text-sm {calculationName === + firstCalculationName + ? rows.find( + (row) => row.calculationName === 'bill', + ) === undefined + ? rows.find( + (row) => row.calculationName === 'amendment', + ) === undefined + ? 'text-gray-500' + : 'text-gray-500 line-through-amendment' + : 'text-gray-500 line-through-bill' + : calculationName === 'bill' + ? rows.find( + (row) => row.calculationName === 'amendment', + ) === undefined + ? 'text-le-rouge-bill' + : 'text-le-rouge-bill line-through-amendment' + : 'bg-le-jaune'}" + >{deltaFormatter( + deltaAtVectorIndexArray[1] ?? 0, + )}</span + > + </div> + </div> + {/if} + {/if} + <!--Montant des dispositifs--> + {:else if deltaAtVectorIndexArray[0] === deltaAtVectorIndexArray[1]} + <!---Composant loader pour la valeur suivante--> + {#if showLoader && runningCalculationNames.length > 0} + {#if runningCalculationNames.includes("law") || runningCalculationNames.includes("revaluation")}<span + class="mx-1 animate-pulse-2 bg-gray-500 px-1 text-black blur-xs" + > + <span class="text-white blur">value €</span> + </span> + {/if} + {#if runningCalculationNames.includes("bill")} + <span + class="mx-1 animate-pulse-2 bg-le-rouge-bill px-1 text-black blur-xs" + > + <span class="text-white blur">value €</span> + </span> + {/if} + {#if runningCalculationNames.includes("amendment") && modificationsAmendmentCount > 0} + <span + class="mx-1 animate-pulse-2 bg-le-jaune px-1 text-black blur-xs" + > + <span class="text-white blur">value €</span> + </span> + {/if} + <!--Valeur--> + {:else} + <span + class="text-base font-bold {calculationName === + firstCalculationName + ? rows.find((row) => row.calculationName === 'bill') === + undefined + ? rows.find( + (row) => row.calculationName === 'amendment', + ) === undefined + ? '' + : 'line-through-amendment' + : 'line-through-bill' + : calculationName === 'bill' + ? rows.find( + (row) => row.calculationName === 'amendment', + ) === undefined + ? 'text-le-rouge-bill' + : 'text-le-rouge-bill line-through-amendment' + : 'bg-le-jaune'}" + >{deltaFormatter(deltaAtVectorIndexArray[0] ?? 0)}</span + > + {/if} + {:else} + <!---Composant loader pour la valeur suivante--> + {#if showLoader && runningCalculationNames.length > 0} + {#if runningCalculationNames.includes("law") || runningCalculationNames.includes("revaluation")}<span + class="mx-1 animate-pulse-2 bg-gray-500 px-1 text-black blur-xs" + > + <span class="text-white blur">value €</span> + </span> + {/if} + {#if runningCalculationNames.includes("bill")} + <span + class="mx-1 animate-pulse-2 bg-le-rouge-bill px-1 text-black blur-xs" + > + <span class="text-white blur">value €</span> + </span> + {/if} + {#if runningCalculationNames.includes("amendment") && modificationsAmendmentCount > 0} + <span + class="mx-1 animate-pulse-2 bg-le-jaune px-1 text-black blur-xs" + > + <span class="text-white blur">value €</span> + </span> + {/if} + <!--Valeur dispositifs--> + {:else} + <div class="flex w-full justify-center"> + <div class="mx-4 w-1/2"> + <span + class="text-base font-bold {calculationName === + firstCalculationName + ? rows.find( + (row) => row.calculationName === 'bill', + ) === undefined + ? rows.find( + (row) => row.calculationName === 'amendment', + ) === undefined + ? '' + : 'line-through-amendment' + : 'line-through-bill' + : calculationName === 'bill' + ? rows.find( + (row) => row.calculationName === 'amendment', + ) === undefined + ? 'text-le-rouge-bill' + : 'text-le-rouge-bill line-through-amendment' + : 'bg-le-jaune'}" + >{deltaFormatter(deltaAtVectorIndexArray[0] ?? 0)}</span + > + </div> + <div class="mx-4 w-1/2"> + <span + class="text-base font-bold {calculationName === + firstCalculationName + ? rows.find( + (row) => row.calculationName === 'bill', + ) === undefined + ? rows.find( + (row) => row.calculationName === 'amendment', + ) === undefined + ? '' + : 'line-through-amendment' + : 'line-through-bill' + : calculationName === 'bill' + ? rows.find( + (row) => row.calculationName === 'amendment', + ) === undefined + ? 'text-le-rouge-bill' + : 'text-le-rouge-bill line-through-amendment' + : 'bg-le-jaune'}" + >{deltaFormatter(deltaAtVectorIndexArray[1] ?? 0)}</span + > + </div> + </div> + {/if} + {/if} + </div> + </div> + {/if} + {/each} + {/each} + </div> +{/if}