From d989df0bdcfd49ebc107fe6b83097a841bcf8b8c Mon Sep 17 00:00:00 2001 From: Emmanuel Raviart <emmanuel@raviart.com> Date: Thu, 24 Nov 2022 19:01:21 +0100 Subject: [PATCH] Add date & plural support to units --- package-lock.json | 73 +++++---- package.json | 7 +- plugin-yaml-patched.ts | 72 +++++++++ src/lib/components/parameters/NodeEdit.svelte | 5 +- .../parameters/ParameterPane.svelte | 17 -- .../parameters/ParameterView.svelte | 20 ++- .../parameters/ReferencesEdit.svelte | 2 +- .../parameters/ScaleAtInstantEdit.svelte | 17 +- .../components/parameters/ScaleEdit.svelte | 10 +- .../parameters/ValueAtInstantEdit.svelte | 8 +- .../components/parameters/ValueEdit.svelte | 6 +- .../VariableReferredScaleAtInstant.svelte | 18 ++- .../VariableReferredScaleParameter.svelte | 1 + .../VariableReferredValueParameter.svelte | 4 +- src/lib/units.ts | 55 ++++--- .../parameters/[parameter]/+page.server.ts | 3 +- .../parameters/[parameter]/+page.svelte | 7 +- .../parameters/[parameter]/edit/+page.svelte | 26 +++- .../variables/[variable]/xlsx/+page.svelte | 146 ++++++++++++------ vite.config.ts | 9 +- 20 files changed, 341 insertions(+), 165 deletions(-) create mode 100644 plugin-yaml-patched.ts delete mode 100644 src/lib/components/parameters/ParameterPane.svelte diff --git a/package-lock.json b/package-lock.json index b7ff436fa..666db6018 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,9 +16,9 @@ "@fontsource/lato": "^4.3.0", "@fontsource/lora": "^4.3.0", "@iconify/svelte": "^3.0.0", - "@leximpact/socio-fiscal-openfisca-json": "^0.0.83", - "@openfisca/json-model": "^2.0.1", - "@playwright/test": "^1.22.2", + "@leximpact/socio-fiscal-openfisca-json": "^0.1.0", + "@openfisca/json-model": "^3.0.0", + "@playwright/test": "^1.28.1", "@rgossiaux/svelte-headlessui": "^1.0.0-beta.12", "@rollup/plugin-yaml": "^4.0.1", "@sveltejs/adapter-node": "^1.0.0-next.101", @@ -28,6 +28,7 @@ "@tricoteuses/legal-explorer": "^0.0.1", "@types/cookie": "^0.5.0", "@types/fs-extra": "^9.0.11", + "@types/js-yaml": "^4.0.5", "@types/uuid": "^8.3.4", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", @@ -2059,12 +2060,12 @@ } }, "node_modules/@leximpact/socio-fiscal-openfisca-json": { - "version": "0.0.83", - "resolved": "https://registry.npmjs.org/@leximpact/socio-fiscal-openfisca-json/-/socio-fiscal-openfisca-json-0.0.83.tgz", - "integrity": "sha512-pwa2kfnVqBCARC/F8EJzBeYSJJCTyX2TaQdplcbIIUC+srJnid+KA0YYpDFsYIih9m0a1nmM+Vzl58G2GkIkpA==", + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@leximpact/socio-fiscal-openfisca-json/-/socio-fiscal-openfisca-json-0.1.0.tgz", + "integrity": "sha512-2q03f4k/1w1+GiEGL0BftLkgSBeSMhhJCx0GV5VLJXvRQoDv7JK9d2JDi/8cINcw6OIKiJ8IRz2B2KsKpohqDg==", "dev": true, "peerDependencies": { - "@openfisca/json-model": "^2.0.1" + "@openfisca/json-model": "^3.0.0" }, "peerDependenciesMeta": { "@openfisca/json-model": { @@ -2108,9 +2109,9 @@ } }, "node_modules/@openfisca/json-model": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@openfisca/json-model/-/json-model-2.0.1.tgz", - "integrity": "sha512-tpPNFww71HW/xI31raU9+QeFU1UuWgDWLkizy0N4kgv3bp7lUVKGngtp+qT4BocOwobPdFZgQUo7jYKGuFOunA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@openfisca/json-model/-/json-model-3.0.0.tgz", + "integrity": "sha512-Cix5anqn0APIIF0CGq7WmfxKrwMGqmibaTfMQNq3KX6+vAtiTcyiwZcIT0n3oKPQn44DNBPHD0e5qV9LkhX+yw==", "dev": true, "dependencies": { "@auditors/core": "^0.3.0", @@ -2123,13 +2124,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.28.0.tgz", - "integrity": "sha512-vrHs5DFTPwYox5SGKq/7TDn/S4q6RA1zArd7uhO6EyP9hj3XgZBBM12ktMbnDQNxh/fL1IUKsTNLxihmsU38lQ==", + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.28.1.tgz", + "integrity": "sha512-xN6spdqrNlwSn9KabIhqfZR7IWjPpFK1835tFNgjrlysaSezuX8PYUwaz38V/yI8TJLG9PkAMEXoHRXYXlpTPQ==", "dev": true, "dependencies": { "@types/node": "*", - "playwright-core": "1.28.0" + "playwright-core": "1.28.1" }, "bin": { "playwright": "cli.js" @@ -2386,6 +2387,12 @@ "@types/node": "*" } }, + "node_modules/@types/js-yaml": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz", + "integrity": "sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==", + "dev": true + }, "node_modules/@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -5550,9 +5557,9 @@ } }, "node_modules/playwright-core": { - "version": "1.28.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.28.0.tgz", - "integrity": "sha512-nJLknd28kPBiCNTbqpu6Wmkrh63OEqJSFw9xOfL9qxfNwody7h6/L3O2dZoWQ6Oxcm0VOHjWmGiCUGkc0X3VZA==", + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.28.1.tgz", + "integrity": "sha512-3PixLnGPno0E8rSBJjtwqTwJe3Yw72QwBBBxNoukIj3lEeBNXwbNiKrNuB1oyQgTBw5QHUhNO3SteEtHaMK6ag==", "dev": true, "bin": { "playwright": "cli.js" @@ -8451,9 +8458,9 @@ } }, "@leximpact/socio-fiscal-openfisca-json": { - "version": "0.0.83", - "resolved": "https://registry.npmjs.org/@leximpact/socio-fiscal-openfisca-json/-/socio-fiscal-openfisca-json-0.0.83.tgz", - "integrity": "sha512-pwa2kfnVqBCARC/F8EJzBeYSJJCTyX2TaQdplcbIIUC+srJnid+KA0YYpDFsYIih9m0a1nmM+Vzl58G2GkIkpA==", + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@leximpact/socio-fiscal-openfisca-json/-/socio-fiscal-openfisca-json-0.1.0.tgz", + "integrity": "sha512-2q03f4k/1w1+GiEGL0BftLkgSBeSMhhJCx0GV5VLJXvRQoDv7JK9d2JDi/8cINcw6OIKiJ8IRz2B2KsKpohqDg==", "dev": true, "requires": {} }, @@ -8484,9 +8491,9 @@ } }, "@openfisca/json-model": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@openfisca/json-model/-/json-model-2.0.1.tgz", - "integrity": "sha512-tpPNFww71HW/xI31raU9+QeFU1UuWgDWLkizy0N4kgv3bp7lUVKGngtp+qT4BocOwobPdFZgQUo7jYKGuFOunA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@openfisca/json-model/-/json-model-3.0.0.tgz", + "integrity": "sha512-Cix5anqn0APIIF0CGq7WmfxKrwMGqmibaTfMQNq3KX6+vAtiTcyiwZcIT0n3oKPQn44DNBPHD0e5qV9LkhX+yw==", "dev": true, "requires": { "@auditors/core": "^0.3.0", @@ -8496,13 +8503,13 @@ } }, "@playwright/test": { - "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.28.0.tgz", - "integrity": "sha512-vrHs5DFTPwYox5SGKq/7TDn/S4q6RA1zArd7uhO6EyP9hj3XgZBBM12ktMbnDQNxh/fL1IUKsTNLxihmsU38lQ==", + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.28.1.tgz", + "integrity": "sha512-xN6spdqrNlwSn9KabIhqfZR7IWjPpFK1835tFNgjrlysaSezuX8PYUwaz38V/yI8TJLG9PkAMEXoHRXYXlpTPQ==", "dev": true, "requires": { "@types/node": "*", - "playwright-core": "1.28.0" + "playwright-core": "1.28.1" } }, "@polka/url": { @@ -8669,6 +8676,12 @@ "@types/node": "*" } }, + "@types/js-yaml": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz", + "integrity": "sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==", + "dev": true + }, "@types/json-schema": { "version": "7.0.11", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", @@ -10912,9 +10925,9 @@ } }, "playwright-core": { - "version": "1.28.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.28.0.tgz", - "integrity": "sha512-nJLknd28kPBiCNTbqpu6Wmkrh63OEqJSFw9xOfL9qxfNwody7h6/L3O2dZoWQ6Oxcm0VOHjWmGiCUGkc0X3VZA==", + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.28.1.tgz", + "integrity": "sha512-3PixLnGPno0E8rSBJjtwqTwJe3Yw72QwBBBxNoukIj3lEeBNXwbNiKrNuB1oyQgTBw5QHUhNO3SteEtHaMK6ag==", "dev": true }, "postcss": { diff --git a/package.json b/package.json index c55eca35e..568728088 100644 --- a/package.json +++ b/package.json @@ -20,9 +20,9 @@ "@fontsource/lato": "^4.3.0", "@fontsource/lora": "^4.3.0", "@iconify/svelte": "^3.0.0", - "@leximpact/socio-fiscal-openfisca-json": "^0.0.83", - "@openfisca/json-model": "^2.0.1", - "@playwright/test": "^1.22.2", + "@leximpact/socio-fiscal-openfisca-json": "^0.1.0", + "@openfisca/json-model": "^3.0.0", + "@playwright/test": "^1.28.1", "@rgossiaux/svelte-headlessui": "^1.0.0-beta.12", "@rollup/plugin-yaml": "^4.0.1", "@sveltejs/adapter-node": "^1.0.0-next.101", @@ -32,6 +32,7 @@ "@tricoteuses/legal-explorer": "^0.0.1", "@types/cookie": "^0.5.0", "@types/fs-extra": "^9.0.11", + "@types/js-yaml": "^4.0.5", "@types/uuid": "^8.3.4", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", diff --git a/plugin-yaml-patched.ts b/plugin-yaml-patched.ts new file mode 100644 index 000000000..97d13837b --- /dev/null +++ b/plugin-yaml-patched.ts @@ -0,0 +1,72 @@ +/// Code taken from https://github.com/rollup/plugins/blob/master/packages/yaml/src/index.js + +import YAML, { type LoadOptions } from "js-yaml" +import toSource from "tosource" +import { + createFilter, + makeLegalIdentifier, + type FilterPattern, +} from "@rollup/pluginutils" +import type { PluginOption } from "vite" + +interface Options extends LoadOptions { + documentMode?: "multi" | "single" + exclude?: FilterPattern | undefined + include?: FilterPattern | undefined + transform?: ((data: unknown, id: string) => unknown) | null +} +const defaults: Options = { + documentMode: "single", + transform: null, +} +const ext = /\.ya?ml$/ + +export default function yaml(opts: Options = {}): PluginOption { + const options = Object.assign({}, defaults, opts) + const { documentMode } = options + const filter = createFilter(options.include, options.exclude) + let loadMethod: (str: string, opts?: LoadOptions) => unknown + + if (documentMode === "single") { + loadMethod = YAML.load + } else if (documentMode === "multi") { + loadMethod = YAML.loadAll as (str: string, opts?: LoadOptions) => unknown + } else { + this.error( + `plugin-yaml → documentMode: '${documentMode}' is not a valid value. Please choose 'single' or 'multi'`, + ) + } + + return { + name: "yaml", + + transform(content: string, id: string) { + if (!ext.test(id)) return null + if (!filter(id)) return null + + // The pach is here: it adds `options` argument. + let data = loadMethod(content, options) + + if (typeof options.transform === "function") { + const result = options.transform(data, id) + // eslint-disable-next-line no-undefined + if (result !== undefined) { + data = result + } + } + + const keys = Object.keys(data).filter( + (key) => key === makeLegalIdentifier(key), + ) + const code = `var data = ${toSource(data)};\n\n` + const exports = ["export default data;"] + .concat(keys.map((key) => `export var ${key} = data.${key};`)) + .join("\n") + + return { + code: code + exports, + map: { mappings: "" }, + } + }, + } +} diff --git a/src/lib/components/parameters/NodeEdit.svelte b/src/lib/components/parameters/NodeEdit.svelte index e4232c378..3dae483bf 100644 --- a/src/lib/components/parameters/NodeEdit.svelte +++ b/src/lib/components/parameters/NodeEdit.svelte @@ -4,8 +4,9 @@ import ReferencesEdit from "$lib/components/parameters/ReferencesEdit.svelte" import { errorAsKeyValueDictionary, iterArrayWithErrors } from "$lib/errors" - import { labelFromUnitName, units } from "$lib/units" + import { getUnitLabel, units } from "$lib/units" + export let date: string let globalErrors: { [key: string]: unknown } export { globalErrors as errors } export let parameter: NodeParameter @@ -228,7 +229,7 @@ <option selected value={undefined}>Non précisée</option> {/if} {#each units as unit} - <option value={unit.name}>{labelFromUnitName(unit.name)}</option> + <option value={unit.name}>{getUnitLabel(unit.name, date)}</option> {/each} </select> {#if showErrors && errors.unit !== undefined} diff --git a/src/lib/components/parameters/ParameterPane.svelte b/src/lib/components/parameters/ParameterPane.svelte deleted file mode 100644 index 9edf42392..000000000 --- a/src/lib/components/parameters/ParameterPane.svelte +++ /dev/null @@ -1,17 +0,0 @@ -<script lang="ts"> - import ParameterView from "$lib/components/parameters/ParameterView.svelte" - import { getParameter, rootParameter } from "$lib/parameters" - - export let name: string - // export let pane: string - - $: parameter = getParameter(rootParameter, name) - - $: if (parameter === undefined) { - console.error(`Parameter "${name}" not found`) - } -</script> - -{#if parameter !== undefined} - <ParameterView {parameter} /> -{/if} diff --git a/src/lib/components/parameters/ParameterView.svelte b/src/lib/components/parameters/ParameterView.svelte index bd9746267..2070a5f9c 100644 --- a/src/lib/components/parameters/ParameterView.svelte +++ b/src/lib/components/parameters/ParameterView.svelte @@ -18,9 +18,10 @@ labelFromScaleType, labelFromValueType, } from "$lib/parameters" - import { shortLabelFromUnitName } from "$lib/units" + import { getUnitShortLabel } from "$lib/units" import type { SelfTargetAProps } from "$lib/urls" + export let date: string export let parameter: Parameter const dateFormatter = new Intl.DateTimeFormat("fr-FR", { dateStyle: "full" }) @@ -191,9 +192,10 @@ : valueAtInstant.value ?? ""}</td > <td class="border p-1 text-center" - >{shortLabelFromUnitName( + >{getUnitShortLabel( valueAtInstant.unit ?? parameter.unit, - ) ?? ""}</td + date, + )}</td > {/if} {#if valueAtInstant !== "expected"} @@ -232,7 +234,7 @@ <div class="font-base my-1 flex border-b py-1 "> <p class="mr-1"> Unité du paramètre :: <span class="font-bold"> - {shortLabelFromUnitName(parameter.unit)}</span + {getUnitShortLabel(parameter.unit, date)}</span > </p> </div> @@ -247,7 +249,7 @@ {#if parameter.threshold_unit !== undefined} <p class="font-base my-1 mr-1 flex py-1"> Unité de seuil : <span class="font-bold"> - {shortLabelFromUnitName(parameter.threshold_unit)}</span + {getUnitShortLabel(parameter.threshold_unit, date)}</span > </p> {/if} @@ -255,8 +257,9 @@ {#if asAmountScaleParameter(parameter).amount_unit !== undefined} <p class="font-base my-1 mr-1 flex py-1"> Unité de montant : <span class="font-bold"> - {shortLabelFromUnitName( + {getUnitShortLabel( asAmountScaleParameter(parameter).amount_unit, + date, )}</span > </p> @@ -264,8 +267,9 @@ {:else if asRateScaleParameter(parameter).rate_unit !== undefined} <p class="font-base my-1 mr-1 flex py-1"> Unité de taux : <span class="font-bold"> - {shortLabelFromUnitName( + {getUnitShortLabel( asRateScaleParameter(parameter).rate_unit, + date, )}</span > </p> @@ -281,7 +285,7 @@ <div class="font-base my-1 flex py-1"> <p class="mr-1"> Unité de la valeur : <span class="font-bold" - >{shortLabelFromUnitName(parameter.unit)}</span + >{getUnitShortLabel(parameter.unit, date)}</span > </p> </div> diff --git a/src/lib/components/parameters/ReferencesEdit.svelte b/src/lib/components/parameters/ReferencesEdit.svelte index 3c066e52a..f3a76c3cb 100644 --- a/src/lib/components/parameters/ReferencesEdit.svelte +++ b/src/lib/components/parameters/ReferencesEdit.svelte @@ -18,7 +18,7 @@ dispatch("change", references) } - function changeReference(index, { detail }: CustomEvent) { + function changeReference(index: number, { detail }: CustomEvent) { const reference = detail as Reference references = [...references] references[index] = reference diff --git a/src/lib/components/parameters/ScaleAtInstantEdit.svelte b/src/lib/components/parameters/ScaleAtInstantEdit.svelte index 463c6a6b1..8bc9b6945 100644 --- a/src/lib/components/parameters/ScaleAtInstantEdit.svelte +++ b/src/lib/components/parameters/ScaleAtInstantEdit.svelte @@ -21,8 +21,9 @@ asRateBracketAtInstant, asRateScaleParameter, } from "$lib/parameters" - import { shortLabelFromUnitName } from "$lib/units" + import { getUnitShortLabel } from "$lib/units" + export let date: string let globalErrors: { [key: string]: unknown } export { globalErrors as errors } export let parameter: ScaleParameter @@ -181,7 +182,7 @@ )} /> <span class="font-serif text-sm text-black"> - {shortLabelFromUnitName(parameter.threshold_unit) ?? ""} + {getUnitShortLabel(parameter.threshold_unit, date)} </span> {#if showErrors && errorAsKeyValueDictionary(errorAsKeyValueDictionary(errorsAtIndex).threshold).value !== undefined} <p> @@ -212,9 +213,10 @@ )} /> <span class="font-serif text-base"> - {shortLabelFromUnitName( + {getUnitShortLabel( asAmountScaleParameter(parameter).amount_unit, - ) ?? ""} + date, + )} </span> {#if showErrors && errorAsKeyValueDictionary(errorAsKeyValueDictionary(errorsAtIndex).amount).value !== undefined} <p> @@ -249,7 +251,7 @@ /> <span class="font-serif text-base"> <!-- TODO: Should be parameter.base_unit. --> - {shortLabelFromUnitName(parameter.threshold_unit) ?? ""} + {getUnitShortLabel(parameter.threshold_unit, date)} </span> {#if showErrors && errorAsKeyValueDictionary(errorAsKeyValueDictionary(errorsAtIndex).base).value !== undefined} <p> @@ -280,9 +282,10 @@ )} /> <span class="font-serif text-base"> - {shortLabelFromUnitName( + {getUnitShortLabel( asRateScaleParameter(parameter).rate_unit, - ) ?? ""} + date, + )} </span> {#if showErrors && errorAsKeyValueDictionary(errorAsKeyValueDictionary(errorsAtIndex).rate).value !== undefined} <p> diff --git a/src/lib/components/parameters/ScaleEdit.svelte b/src/lib/components/parameters/ScaleEdit.svelte index 07896e686..b7c39d6ab 100644 --- a/src/lib/components/parameters/ScaleEdit.svelte +++ b/src/lib/components/parameters/ScaleEdit.svelte @@ -21,8 +21,9 @@ buildInstantReferencesAndScaleArray, labelFromScaleType, } from "$lib/parameters" - import { labelFromUnitName, units } from "$lib/units" + import { getUnitLabel, units } from "$lib/units" + export let date: string let globalErrors: { [key: string]: unknown } export { globalErrors as errors } export let parameter: ScaleParameter @@ -193,6 +194,7 @@ <div class="mb-4 p-2 text-xs text-gray-500"> {#if scaleAtInstant !== undefined} <ScaleAtInstantEdit + {date} errors={errorAsKeyValueDictionary(error?.[1])} on:change={(event) => changeScaleAtInstant(index, event)} {parameter} @@ -301,7 +303,7 @@ <option selected value={undefined}>Non précisée</option> {/if} {#each units as unit} - <option value={unit.name}>{labelFromUnitName(unit.name)}</option> + <option value={unit.name}>{getUnitLabel(unit.name, date)}</option> {/each} </select> {#if showErrors && errors.amount_unit !== undefined} @@ -321,7 +323,7 @@ <option selected value={undefined}>Non précisée</option> {/if} {#each units as unit} - <option value={unit.name}>{labelFromUnitName(unit.name)}</option> + <option value={unit.name}>{getUnitLabel(unit.name, date)}</option> {/each} </select> {#if showErrors && errors.rate_unit !== undefined} @@ -378,7 +380,7 @@ <option selected value={undefined}>Non précisée</option> {/if} {#each units as unit} - <option value={unit.name}>{labelFromUnitName(unit.name)}</option> + <option value={unit.name}>{getUnitLabel(unit.name, date)}</option> {/each} </select> {#if showErrors && errors.threshold_unit !== undefined} diff --git a/src/lib/components/parameters/ValueAtInstantEdit.svelte b/src/lib/components/parameters/ValueAtInstantEdit.svelte index da89dcfac..e3a341ce2 100644 --- a/src/lib/components/parameters/ValueAtInstantEdit.svelte +++ b/src/lib/components/parameters/ValueAtInstantEdit.svelte @@ -19,8 +19,9 @@ import { createEventDispatcher } from "svelte" import { auditEditedAttribute } from "$lib/errors" - import { labelFromUnitName, shortLabelFromUnitName, units } from "$lib/units" + import { getUnitLabel, units } from "$lib/units" + export let date: string let globalErrors: { [key: string]: unknown } export { globalErrors as errors } export let parameter: ValueParameter @@ -156,14 +157,15 @@ value={asMaybeNumberValue(valueAtInstant).unit} > <option value={undefined}> - {#if parameter.unit == null}Non précisée{:else}{shortLabelFromUnitName( + {#if parameter.unit == null}Non précisée{:else}{getUnitLabel( parameter.unit, + date, )} {/if}</option > {#each units as unit} {#if unit.name !== parameter.unit} - <option value={unit.name}>{labelFromUnitName(unit.name)}</option> + <option value={unit.name}>{getUnitLabel(unit.name, date)}</option> {/if} {/each} </select> diff --git a/src/lib/components/parameters/ValueEdit.svelte b/src/lib/components/parameters/ValueEdit.svelte index a5e1f3ae5..bbe0fbc07 100644 --- a/src/lib/components/parameters/ValueEdit.svelte +++ b/src/lib/components/parameters/ValueEdit.svelte @@ -15,8 +15,9 @@ buildInstantReferencesAndValueArray, labelFromValueType, } from "$lib/parameters" - import { labelFromUnitName, units } from "$lib/units" + import { getUnitLabel, units } from "$lib/units" + export let date: string let globalErrors: { [key: string]: unknown } export { globalErrors as errors } export let parameter: ValueParameter @@ -172,6 +173,7 @@ <div class="mb-4 p-2 text-xs text-gray-500"> {#if valueAtInstant !== undefined} <ValueAtInstantEdit + {date} errors={errorAsKeyValueDictionary(error?.[1])} on:change={(event) => changeValueAtInstant(index, event)} {parameter} @@ -298,7 +300,7 @@ <option selected value={undefined}>Non précisée</option> {/if} {#each units as unit} - <option value={unit.name}>{labelFromUnitName(unit.name)}</option> + <option value={unit.name}>{getUnitLabel(unit.name, date)}</option> {/each} </select> {#if showErrors && errors.unit !== undefined} diff --git a/src/lib/components/variables/VariableReferredScaleAtInstant.svelte b/src/lib/components/variables/VariableReferredScaleAtInstant.svelte index 7249b159e..17ac04779 100644 --- a/src/lib/components/variables/VariableReferredScaleAtInstant.svelte +++ b/src/lib/components/variables/VariableReferredScaleAtInstant.svelte @@ -23,12 +23,13 @@ asRateBracketAtInstantOrNullable, asRateScaleParameter, } from "$lib/parameters" - import { shortLabelFromUnitName } from "$lib/units" + import { getUnitShortLabel } from "$lib/units" import VariableReferredValueEdit from "./VariableReferredValueEdit.svelte" export let billParameter: ScaleParameter export let billScaleAtInstant: ScaleAtInstant | null + export let date: string let globalErrors: { [key: string]: unknown } export { globalErrors as errors } export let lawScaleAtInstant: ScaleAtInstant | undefined | null @@ -171,7 +172,7 @@ : bracketAtInstant.threshold?.value ?? null} /> <span class="ml-1 text-base"> - {shortLabelFromUnitName(billParameter.threshold_unit) ?? ""} + {getUnitShortLabel(billParameter.threshold_unit, date)} </span> {#if showErrors && errorAsKeyValueDictionary(errorAsKeyValueDictionary(errorsAtIndex).threshold).value !== undefined} <p> @@ -203,9 +204,10 @@ ).value ?? null} /> <span class="text-base"> - {shortLabelFromUnitName( + {getUnitShortLabel( asAmountScaleParameter(billParameter).amount_unit, - ) ?? ""} + date, + )} </span> {#if showErrors && errorAsKeyValueDictionary(errorAsKeyValueDictionary(errorsAtIndex).amount).value !== undefined} <p> @@ -240,8 +242,7 @@ /> <span class="ml-1 text-base"> <!-- TODO: Should be parameter.base_unit. --> - {shortLabelFromUnitName(billParameter.threshold_unit) ?? - ""} + {getUnitShortLabel(billParameter.threshold_unit, date)} </span> {#if showErrors && errorAsKeyValueDictionary(errorAsKeyValueDictionary(errorsAtIndex).base).value !== undefined} <p> @@ -274,9 +275,10 @@ /> <span class="ml-1 text-base"> <!-- TODO: Should be parameter.base_unit. --> - {shortLabelFromUnitName( + {getUnitShortLabel( asRateScaleParameter(billParameter).rate_unit, - ) ?? ""} + date, + )} </span> {#if showErrors && errorAsKeyValueDictionary(errorAsKeyValueDictionary(errorsAtIndex).rate).value !== undefined} <p> diff --git a/src/lib/components/variables/VariableReferredScaleParameter.svelte b/src/lib/components/variables/VariableReferredScaleParameter.svelte index 0b7e07efa..230494d9c 100644 --- a/src/lib/components/variables/VariableReferredScaleParameter.svelte +++ b/src/lib/components/variables/VariableReferredScaleParameter.svelte @@ -123,6 +123,7 @@ <VariableReferredScaleAtInstant {billParameter} {billScaleAtInstant} + {date} errors={{}} {lawScaleAtInstant} on:change={changeScale} diff --git a/src/lib/components/variables/VariableReferredValueParameter.svelte b/src/lib/components/variables/VariableReferredValueParameter.svelte index 5e52ca09d..320a3835a 100644 --- a/src/lib/components/variables/VariableReferredValueParameter.svelte +++ b/src/lib/components/variables/VariableReferredValueParameter.svelte @@ -15,7 +15,7 @@ import ArticleModal from "$lib/components/parameters/ArticleModal.svelte" import type { ParametricReform, ValueParameterReform } from "$lib/reforms" import { ParameterReformChangeType } from "$lib/reforms" - import { shortLabelFromUnitName } from "$lib/units" + import { getUnitShortLabel } from "$lib/units" import VariableReferredParameterHeader from "./VariableReferredParameterHeader.svelte" import VariableReferredValueEdit from "./VariableReferredValueEdit.svelte" @@ -140,7 +140,7 @@ /> </div> <span class="ml-1 text-base"> - {shortLabelFromUnitName(billParameter.unit) ?? ""} + {getUnitShortLabel(billParameter.unit, date)} </span> {#if valueError !== null}<p class="text-red-500">{valueError}</p>{/if} diff --git a/src/lib/units.ts b/src/lib/units.ts index 1998dd755..ec3a87fc4 100644 --- a/src/lib/units.ts +++ b/src/lib/units.ts @@ -1,33 +1,38 @@ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import unitsUnknown from "@leximpact/socio-fiscal-openfisca-json/units.yaml" -import type { UnitsMetadata } from "@openfisca/json-model" +import { + getUnitLabel as getUnitLabelOriginal, + getUnitShortLabel as getUnitShortLabelOriginal, + type Unit, +} from "@openfisca/json-model" -export function labelFromUnitName(unitName: string | undefined | null): string { - if (unitName == null) { - return "" - } - const unit = unitByName[unitName] - if (unit === undefined) { - return unitName - } - return unit.label ?? unitName -} +const frenchPluralRules = new Intl.PluralRules(["fr-FR"]) +export const units = unitsUnknown as Unit[] +const unitByName = Object.fromEntries(units.map((unit) => [unit.name, unit])) -export function shortLabelFromUnitName( - unitName: string | undefined | null, +export function getUnitLabel( + name: string | undefined | null, + date: string, + value = 123, ): string { - if (unitName == null) { - return "" - } - const unit = unitByName[unitName] - if (unit === undefined) { - return unitName - } - return unit.short_label ?? unit.label ?? unitName + return getUnitLabelOriginal( + unitByName, + name, + date, + frenchPluralRules.select(value ?? 0), + ) } -export const units = unitsUnknown as UnitsMetadata -export const unitByName = Object.fromEntries( - units.map((unit) => [unit.name, unit]), -) +export function getUnitShortLabel( + name: string | undefined | null, + date: string, + value = 123, +): string { + return getUnitShortLabelOriginal( + unitByName, + name, + date, + frenchPluralRules.select(value ?? 0), + ) +} diff --git a/src/routes/parameters/[parameter]/+page.server.ts b/src/routes/parameters/[parameter]/+page.server.ts index f4b37282b..5124df099 100644 --- a/src/routes/parameters/[parameter]/+page.server.ts +++ b/src/routes/parameters/[parameter]/+page.server.ts @@ -11,6 +11,7 @@ import { randomBytes } from "crypto" import { getParameter, rootParameter } from "$lib/parameters" import config from "$lib/server/config" +import { units } from "$lib/units" import type { Action } from "./$types" @@ -29,7 +30,7 @@ export const PUT: Action = async ({ request, params }) => { if (parameter === undefined) { throw error(404, `Paramètre ${name} non trouvé`) } - const [validParameter, parameterError] = auditEditableParameter( + const [validParameter, parameterError] = auditEditableParameter(units)( strictAudit, await request.json(), ) diff --git a/src/routes/parameters/[parameter]/+page.svelte b/src/routes/parameters/[parameter]/+page.svelte index 219faef14..5726f239b 100644 --- a/src/routes/parameters/[parameter]/+page.svelte +++ b/src/routes/parameters/[parameter]/+page.svelte @@ -1,5 +1,6 @@ <script lang="ts"> - import { setContext } from "svelte" + import { getContext, setContext } from "svelte" + import type { Writable } from "svelte/store" import ParameterView from "$lib/components/parameters/ParameterView.svelte" import { newSelfTargetAProps } from "$lib/urls" @@ -8,6 +9,8 @@ export let data: PageData + const date = getContext("date") as Writable<string> + $: ({ parameter } = data) setContext("newSelfTargetAProps", newSelfTargetAProps) @@ -17,4 +20,4 @@ <title>{parameter.name} | Paramètres | {data.title}</title> </svelte:head> -<ParameterView {parameter} /> +<ParameterView date={$date} {parameter} /> diff --git a/src/routes/parameters/[parameter]/edit/+page.svelte b/src/routes/parameters/[parameter]/edit/+page.svelte index da99cd0e2..910bd5f3d 100644 --- a/src/routes/parameters/[parameter]/edit/+page.svelte +++ b/src/routes/parameters/[parameter]/edit/+page.svelte @@ -10,18 +10,22 @@ convertEditableParameterToRaw, yamlFromRawParameter, } from "@openfisca/json-model" + import { getContext } from "svelte" + import type { Writable } from "svelte/store" import { goto } from "$app/navigation" import NodeEdit from "$lib/components/parameters/NodeEdit.svelte" import ScaleEdit from "$lib/components/parameters/ScaleEdit.svelte" import ValueEdit from "$lib/components/parameters/ValueEdit.svelte" import { labelFromParameterClass } from "$lib/parameters" + import { units } from "$lib/units" import { newSelfTargetAProps } from "$lib/urls" import type { PageData } from "./$types" export let data: PageData + const date = getContext("date") as Writable<string> let { parameter, processedParameter } = data let errors: { [key: string]: unknown } = {} @@ -54,7 +58,7 @@ } $: ((parameter) => { - const [validParameter, parameterError] = auditEditableParameter( + const [validParameter, parameterError] = auditEditableParameter(units)( laxAudit, parameter, ) @@ -64,7 +68,7 @@ })(parameter) async function save() { - const [validParameter, parameterError] = auditEditableParameter( + const [validParameter, parameterError] = auditEditableParameter(units)( laxAudit, parameter, ) @@ -241,11 +245,23 @@ </div> {#if parameter.class === ParameterClass.Node} - <NodeEdit {errors} bind:parameter {showErrors} /> + <NodeEdit date={$date} {errors} bind:parameter {showErrors} /> {:else if parameter.class === ParameterClass.Scale} - <ScaleEdit {errors} bind:parameter bind:reviewed {showErrors} /> + <ScaleEdit + date={$date} + {errors} + bind:parameter + bind:reviewed + {showErrors} + /> {:else if parameter.class === ParameterClass.Value} - <ValueEdit {errors} bind:parameter bind:reviewed {showErrors} /> + <ValueEdit + date={$date} + {errors} + bind:parameter + bind:reviewed + {showErrors} + /> {/if} {#if parameter.referring_variables !== undefined} diff --git a/src/routes/variables/[variable]/xlsx/+page.svelte b/src/routes/variables/[variable]/xlsx/+page.svelte index 4f91b36ca..ef0220a59 100644 --- a/src/routes/variables/[variable]/xlsx/+page.svelte +++ b/src/routes/variables/[variable]/xlsx/+page.svelte @@ -163,7 +163,7 @@ // const token = crypto.randomUUID() const token = uuidV4() calculationByName.law = { running: true, token } - webSocketByName.law.send( + webSocketByName?.law.send( JSON.stringify({ ...message, situation: aggregatedSituation, @@ -242,6 +242,7 @@ WBProps: { date1904: true }, } + // Collect infos on persons & groups. const columnIndexByEntityKey: { [key: string]: number } = {} const groupInfosByIdByEntityKeyBySituationIndex: { [situationIndex: number]: { @@ -250,15 +251,20 @@ } } } = {} + const maxPersonsCountByGroupEntityKey: { + [groupEntityKey: string]: number + } = {} const personIndexByIdBySituationIndex: { [situationIndex: number]: { [personId: string]: number } } = {} - const refByName: { [name: string]: string } = {} - const roleByGroupEntityKeyByPersonIdBySituationIndex: { + // const refByName: { [name: string]: string } = {} + const personInfosByGroupEntityKeyByPersonIdBySituationIndex: { [situationIndex: number]: { - [personId: string]: { [groupEntityKey: string]: string } + [personId: string]: { + [groupEntityKey: string]: { groupId: string; role: string } + } } } = {} const rowIndexByEntityKey: { [entityKey: string]: number } = {} @@ -273,11 +279,13 @@ groupInfosByIdByEntityKey const personIndexById: { [personId: string]: number } = {} personIndexByIdBySituationIndex[situationIndex] = personIndexById - const roleByGroupEntityKeyByPersonId: { - [personId: string]: { [groupEntityKey: string]: string } + const personInfosByGroupEntityKeyByPersonId: { + [personId: string]: { + [groupEntityKey: string]: { groupId: string; role: string } + } } = {} - roleByGroupEntityKeyByPersonIdBySituationIndex[situationIndex] = - roleByGroupEntityKeyByPersonId + personInfosByGroupEntityKeyByPersonIdBySituationIndex[situationIndex] = + personInfosByGroupEntityKeyByPersonId for (const [entityKey, entity] of Object.entries(entityByKey)) { const groupInfosById: { [groupId: string]: { groupIndex: number; personsId: string[] } @@ -306,25 +314,34 @@ const flattenedRole = flattenedRolesGenerator.next() .value as RoleBase - let roleByGroupEntityKey = - roleByGroupEntityKeyByPersonId[personId] - if (roleByGroupEntityKey === undefined) { - roleByGroupEntityKey = roleByGroupEntityKeyByPersonId[ - personId - ] = {} + let personInfosByGroupEntityKey = + personInfosByGroupEntityKeyByPersonId[personId] + if (personInfosByGroupEntityKey === undefined) { + personInfosByGroupEntityKey = + personInfosByGroupEntityKeyByPersonId[personId] = {} + } + personInfosByGroupEntityKey[entityKey] = { + groupId: populationId, + role: flattenedRole.key, } - roleByGroupEntityKey[entityKey] = flattenedRole.key } } groupInfosById[populationId] = { groupIndex: rowIndex, personsId, } + const personsCount = personsId.length + if ( + personsCount > (maxPersonsCountByGroupEntityKey[entityKey] ?? 0) + ) { + maxPersonsCountByGroupEntityKey[entityKey] = personsCount + } } } } } + // Generate tableByEntityKey. const variableByName = Object.fromEntries( variables.map((variable) => [variable.name, variable]), ) @@ -347,9 +364,17 @@ entityByKey, ).sort(([key1], [key2]) => key1.localeCompare(key2))) { if (!groupEntity.is_person) { - header.push(`role_dans_${groupEntityKey}`) + header.push(groupEntityKey, `role_dans_${groupEntityKey}`) } } + } else { + for ( + let i = 1; + i <= (maxPersonsCountByGroupEntityKey[entityKey] ?? 0); + i++ + ) { + header.push(`membre_${i}_${entityKey}`) + } } table = tableByEntityKey[entityKey] = [header] for (const [situationIndex, situation] of $testCases.entries()) { @@ -361,8 +386,10 @@ groupInfosByIdByEntityKeyBySituationIndex[situationIndex] const personIndexById = personIndexByIdBySituationIndex[situationIndex] - const roleByGroupEntityKeyByPersonId = - roleByGroupEntityKeyByPersonIdBySituationIndex[situationIndex] + const personInfosByGroupEntityKeyByPersonId = + personInfosByGroupEntityKeyByPersonIdBySituationIndex[ + situationIndex + ] for (const [populationIndex, populationId] of Object.keys( entitySituation, ) @@ -380,35 +407,68 @@ populationId, ] if (entity.is_person) { - const personIndex = personIndexById[populationId] - refByName[uid] = `${entityKey}!$${personIndex + 1}:$${ - personIndex + 1 - }` + // const personIndex = personIndexById[populationId] + const personInfosByGroupEntityKey = + personInfosByGroupEntityKeyByPersonId[populationId] + // refByName[uid] = `${entityKey}!$${personIndex + 1}:$${ + // personIndex + 1 + // }` for (const [groupEntityKey, groupEntity] of Object.entries( entityByKey, ).sort(([key1], [key2]) => key1.localeCompare(key2))) { if (!groupEntity.is_person) { + const personInfos = + personInfosByGroupEntityKey[groupEntityKey] + const groupInfos = + groupInfosByIdByEntityKey[groupEntityKey][ + personInfos.groupId + ] row.push( - roleByGroupEntityKeyByPersonId[populationId][ - groupEntityKey - ], + { + f: `ROW(${groupEntityKey}!$${ + groupInfos.groupIndex + 1 + }:$${groupInfos.groupIndex + 1})`, + t: "n", + v: groupInfos.groupIndex + 1, + }, + personInfos.role, ) } } } else { const groupInfos = groupInfosByIdByEntityKey[entityKey][populationId] - refByName[uid] = `${entityKey}!$${groupInfos.groupIndex + 1}:$${ - groupInfos.groupIndex + 1 - }` - refByName[`${uid}_membres`] = groupInfos.personsId - .map((personId) => { + for ( + let i = 0; + i < (maxPersonsCountByGroupEntityKey[entityKey] ?? 0); + i++ + ) { + const personId = groupInfos.personsId[i] + if (personId === undefined) { + row.push(undefined) + } else { const personIndex = personIndexById[personId] - return `${personEntityKey}!$${personIndex + 1}:$${ - personIndex + 1 - }` - }) - .join(":") + // Note: A formula is used to allow value update when rows are inserted or deleted. + row.push({ + f: `ROW(${personEntityKey}!$${personIndex + 1}:$${ + personIndex + 1 + })`, + t: "n", + v: personIndex + 1, + }) + } + } + // refByName[uid] = `${entityKey}!$${groupInfos.groupIndex + 1}:$${ + // groupInfos.groupIndex + 1 + // }` + // refByName[`${uid}_membres`] = groupInfos.personsId + // .map((personId) => { + // const personIndex = personIndexById[personId] + // return `${personEntityKey}!$${personIndex + 1}:$${ + // personIndex + 1 + // }` + // }) + // .join(":") } table.push(row) } @@ -560,14 +620,14 @@ } // Add rows-related named ranges. - for (const [name, ref] of Object.entries(refByName)) { - workbook.Workbook.Names.push({ - // Comment: - Name: name, - Ref: ref, - Sheet: null, - }) - } + // for (const [name, ref] of Object.entries(refByName)) { + // workbook.Workbook.Names.push({ + // // Comment: + // Name: name, + // Ref: ref, + // Sheet: null, + // }) + // } XLSX.writeFile( workbook, diff --git a/vite.config.ts b/vite.config.ts index ca0833bae..387d5b333 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,6 +1,9 @@ import { sveltekit } from "@sveltejs/kit/vite" import type { UserConfig } from "vite" -import yaml from "@rollup/plugin-yaml" +import yaml from "js-yaml" +// import yamlPlugin from "@rollup/plugin-yaml" + +import yamlPlugin from "./plugin-yaml-patched" const config: UserConfig = { build: { @@ -14,7 +17,9 @@ const config: UserConfig = { exclude: ["svelte-modals"], }, plugins: [ - yaml(), // To import YAML files + yamlPlugin({ + schema: yaml.JSON_SCHEMA, // Keep dates as strings. + }), // To import YAML files sveltekit(), ], ssr: { -- GitLab