diff --git a/src/global.d.ts b/src/global.d.ts index 79d7d7faa6070e16e9fe43ac78c2ed5882f31cc6..c57bce8c6b32f0dd8c723c951f37aa6d1631cc35 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -1,3 +1,18 @@ /// <reference types="@sveltejs/kit" /> /// <reference types="svelte" /> /// <reference types="vite/client" /> + +// See: https://github.com/isaacHagoel/svelte-dnd-action#typescript +// Needed until following bug is solved: +// https://github.com/sveltejs/language-tools/issues/431. +declare type DndEvent = import("svelte-dnd-action").DndEvent +declare namespace svelte.JSX { + interface HTMLAttributes<T> { + onconsider?: ( + event: CustomEvent<DndEvent> & { target: EventTarget & T }, + ) => void + onfinalize?: ( + event: CustomEvent<DndEvent> & { target: EventTarget & T }, + ) => void + } +} diff --git a/src/lib/components/situations/RolePersonsEdit.svelte b/src/lib/components/situations/RolePersonsEdit.svelte index 6486f6c0a74944de624cadda163f92aadad1d35e..51509564355c5ffce524c6aa62e423c4138f272e 100644 --- a/src/lib/components/situations/RolePersonsEdit.svelte +++ b/src/lib/components/situations/RolePersonsEdit.svelte @@ -3,52 +3,72 @@ import { flip } from "svelte/animate" import { dndzone } from "svelte-dnd-action" + import type { PersonSituation } from "$lib/situations" + export let idPrefix: string - export let names: string[] | undefined | null + export let personById: { [id: string]: PersonSituation } + export let personsId: string[] | undefined | null export let type: string + interface Item { + id: string + personId: string + } + const dispatch = createEventDispatcher() - const existingItemByName = {} + const existingItemByPersonId = {} let existingItems = [] - let existingNames = [] + let existingPersonsId = [] const flipDurationMs = 300 - $: items = computeItems(names) + $: items = computeItems(personsId) /// Create items from names, but try to reuse existing items when possible. /// Otherwise, svelte-dnd-action fails. - function computeItems(names: string[] | undefined | null) { - if (names == null) { + function computeItems(personsId: string[] | undefined | null): Item[] { + if (personsId == null) { return [] } if ( - names.length === existingNames.length && - names.every((name, index) => name === existingNames[index]) + personsId.length === existingPersonsId.length && + personsId.every((id, index) => id === existingPersonsId[index]) ) { return existingItems } - const items = names.map((name) => { - let item = existingItemByName[name] + const items = personsId.map((id) => { + let item = existingItemByPersonId[id] if (item === undefined) { - item = existingItemByName[name] = { id: `${idPrefix}.${name}`, name } + item = existingItemByPersonId[id] = { + id: `${idPrefix}.${id}`, + personId: id, + } } return item }) - existingNames = names + existingPersonsId = personsId existingItems = items return items } function handleDndConsider({ detail }: CustomEvent<DndEvent>) { existingItems = detail.items - existingNames = detail.items.map((item) => item.name) - dispatch("change", existingNames) + existingPersonsId = detail.items.map((item) => item.personId) + dispatch("change", existingPersonsId) } function handleDndFinalize({ detail }: CustomEvent<DndEvent>) { existingItems = detail.items - existingNames = detail.items.map((item) => item.name) - dispatch("change", existingNames) + existingPersonsId = detail.items.map((item) => item.personId) + dispatch("change", existingPersonsId) + } + + function* iterItemIdAndPersonCouples( + personById: { [id: string]: PersonSituation }, + items: Item[], + ): Generator<{ id: string; person: PersonSituation }, void, unknown> { + for (const { id, personId } of items) { + yield { id, person: personById[personId] } + } } </script> @@ -62,7 +82,9 @@ type, }} > - {#each items as { id, name } (id)} - <li animate:flip={{ duration: flipDurationMs }} class="ml-4">{name}</li> + {#each [...iterItemIdAndPersonCouples(personById, items)] as { id, person } (id)} + <li animate:flip={{ duration: flipDurationMs }} class="ml-4"> + {person.name ?? person.id} + </li> {/each} </ul> diff --git a/src/lib/components/situations/SituationEdit.svelte b/src/lib/components/situations/SituationEdit.svelte index 54ec3d6e40b10cd178aaa7afe78671037342717a..8cfb5022f15fe549abbcd830d41b556005b65afd 100644 --- a/src/lib/components/situations/SituationEdit.svelte +++ b/src/lib/components/situations/SituationEdit.svelte @@ -1,102 +1,141 @@ <script lang="ts"> - import type { EntityByKey, Person, Group } from "@openfisca/ast" + import type { EntityByKey, Group, Person, Role } from "@openfisca/ast" import { session } from "$app/stores" - import type { Situation } from "$lib/situations" + import RolePersonsEdit from "$lib/components/situations/RolePersonsEdit.svelte" + import type { PersonSituationWithoutId, Situation } from "$lib/situations" export let situation: Situation + const flipDurationMs = 300 + $: entityByKey = $session.entityByKey as EntityByKey $: personEntityKey = $session.personEntityKey as string $: personEntity = entityByKey[personEntityKey] as Person - $: personByName = situation[personEntity.key_plural] ?? {} + $: persons = Object.entries( + (situation[personEntity.key_plural] ?? {}) as { + [id: string]: PersonSituationWithoutId + }, + ).map(([personId, person]) => ({ + ...person, + id: personId, + })) + + $: personById = Object.fromEntries( + persons.map((person) => [person.id, person]), + ) function addEntityInstance(key: string): void { const entity = entityByKey[key] - const entityInstanceName = `${entity.label ?? entity.key} ${ + const entityInstanceId = `${entity.label ?? entity.key} ${ Object.keys(situation[entity.key_plural]).length + 1 }` - const entityInstanceByName = { + const entityInstanceById = { ...situation[entity.key_plural], - [entityInstanceName]: {}, + [entityInstanceId]: {}, } - situation = { ...situation, [entity.key_plural]: entityInstanceByName } + situation = { ...situation, [entity.key_plural]: entityInstanceById } if (entity.is_person) { - for (const group of Object.values(entityByKey) as Group[]) { - if (group.is_person) { + for (const groupEntity of Object.values(entityByKey) as Group[]) { + if (groupEntity.is_person) { continue } - const groupInstanceByName: - | { [name: string]: { [roleKeyPlural: string]: string[] } } - | undefined = situation[group.key_plural] - if (groupInstanceByName === undefined) { + const groupById: + | { [id: string]: { [roleKeyPlural: string]: string[] } } + | undefined = situation[groupEntity.key_plural] + if (groupById === undefined) { continue } - const groupInstanceNameAndInstanceCouples = - Object.entries(groupInstanceByName) - if (groupInstanceNameAndInstanceCouples.length === 0) { + const idAndGroupCouples = Object.entries(groupById) + if (idAndGroupCouples.length === 0) { continue } - const [lastGroupInstanceName, lastGroupInstance] = - groupInstanceNameAndInstanceCouples[ - groupInstanceNameAndInstanceCouples.length - 1 - ] + const [lastGroupId, lastGroup] = + idAndGroupCouples[idAndGroupCouples.length - 1] let foundRoleIndex: number = 0 - for (const [roleIndex, role] of [...group.roles.entries()].reverse()) { + for (const [roleIndex, role] of [ + ...groupEntity.roles.entries(), + ].reverse()) { const roleKey = role.max === undefined || role.max > 1 ? role.key_plural : role.key - const rolePersonsName = lastGroupInstance[roleKey] - if (rolePersonsName !== undefined && rolePersonsName.length > 0) { + const rolePersonsId = lastGroup[roleKey] + if (rolePersonsId !== undefined && rolePersonsId.length > 0) { foundRoleIndex = roleIndex break } } - let completedRolePersonsName: string[] | undefined = undefined - for (; foundRoleIndex < group.roles.length; foundRoleIndex++) { - const role = group.roles[foundRoleIndex] + let completedRolePersonsId: string[] | undefined = undefined + for (; foundRoleIndex < groupEntity.roles.length; foundRoleIndex++) { + const role = groupEntity.roles[foundRoleIndex] const roleKey = role.max === undefined || role.max > 1 ? role.key_plural : role.key - const rolePersonsName = lastGroupInstance[roleKey] - if (rolePersonsName === undefined) { - completedRolePersonsName = [entityInstanceName] + const rolePersonsId = lastGroup[roleKey] + if (rolePersonsId === undefined) { + completedRolePersonsId = [entityInstanceId] break } - if (role.max === undefined || rolePersonsName.length < role.max) { - completedRolePersonsName = [...rolePersonsName, entityInstanceName] + if (role.max === undefined || rolePersonsId.length < role.max) { + completedRolePersonsId = [...rolePersonsId, entityInstanceId] break } } - if (completedRolePersonsName !== undefined) { - const role = group.roles[foundRoleIndex] + if (completedRolePersonsId !== undefined) { + const role = groupEntity.roles[foundRoleIndex] const roleKey = role.max === undefined || role.max > 1 ? role.key_plural : role.key - situation[group.key_plural] = { - ...groupInstanceByName, - [lastGroupInstanceName]: { - ...lastGroupInstance, - [roleKey]: completedRolePersonsName, + situation[groupEntity.key_plural] = { + ...groupById, + [lastGroupId]: { + ...lastGroup, + [roleKey]: completedRolePersonsId, }, } } } } } + + function changeRolePersonsId( + groupEntityKeyUsed: string, + groupId: string, + roleKeyUsed: string, + personsId: string[], + ): void { + situation = { + ...situation, + [groupEntityKeyUsed]: { + ...situation[groupEntityKeyUsed], + [groupId]: { + ...situation[groupEntityKeyUsed][groupId], + [roleKeyUsed]: personsId, + }, + }, + } + } + + function* iterGroupEntities( + entityByKey: EntityByKey, + ): Generator<Group, void, unknown> { + for (const group of Object.values(entityByKey)) { + if (!group.is_person) { + yield group as Group + } + } + } </script> <section> <h1>{personEntity.label_plural ?? personEntity.key_plural}</h1> - <ol> - {#each Object.entries(personByName) as [personName, person], index (`${personEntity.key_plural}.${index}`)} - <li> - {personName} - <pre>{JSON.stringify(person, null, 2)}</pre> - </li> + <dl> + {#each persons as person, index (`${personEntity.key_plural}.${index}`)} + <dt>{person.name ?? person.id}</dt> + <dd><pre>{JSON.stringify(person, null, 2)}</pre></dd> {/each} - </ol> + </dl> <button class="bg-le-bleu hover:bg-blue-900 p-1 rounded shadow-md text-white" on:click={() => addEntityInstance(personEntityKey)} @@ -104,21 +143,53 @@ > </section> -{#each Object.values(entityByKey) as entity} - {#if !entity.is_person} +{#each [...iterGroupEntities(entityByKey)] as groupEntity} + {#if !groupEntity.is_person} <section> - <h1>{entity.label_plural ?? entity.key_plural}</h1> - <ol> - {#each Object.entries(situation[entity.key_plural] ?? {}) as [name, instance], index (`${entity.key_plural}.${index}`)} - <li> - {name} - <pre>{JSON.stringify(instance, null, 2)}</pre> - </li> + <h1 class="font-bold my-4 text-xl"> + {groupEntity.label_plural ?? groupEntity.key_plural} + </h1> + <dl> + {#each Object.entries(situation[groupEntity.key_plural] ?? {}) as [groupId, group], index (`${groupEntity.key_plural}.${index}`)} + <dt>{groupId}</dt> + <dd class="ml-4"> + <dl> + {#each groupEntity.roles as role} + <dt> + {role.max === undefined || role.max > 1 + ? role.label_plural ?? role.key_plural + : role.label ?? role.key} + </dt> + <dd class="ml-4"> + <RolePersonsEdit + idPrefix="{groupEntity.key_plural}.{groupId}.{role.key}" + {personById} + personsId={group[ + role.max === undefined || role.max > 1 + ? role.key_plural + : role.key + ]} + on:change={({ detail }) => + changeRolePersonsId( + groupEntity.key_plural, + groupId, + role.max === undefined || role.max > 1 + ? role.key_plural + : role.key, + detail, + )} + type={groupEntity.key_plural} + /> + </dd> + {/each} + </dl> + <pre>{JSON.stringify(group, null, 2)}</pre> + </dd> {/each} - </ol> + </dl> <button class="bg-le-bleu hover:bg-blue-900 p-1 rounded shadow-md text-white" - on:click={() => addEntityInstance(entity.key)} + on:click={() => addEntityInstance(groupEntity.key)} type="button">+</button > </section> diff --git a/src/lib/situations.ts b/src/lib/situations.ts index 575fe21417be86cfd6a8926856b13b715341e51b..4c3220fc18040b9e5b2881195b14ff2a4121955e 100644 --- a/src/lib/situations.ts +++ b/src/lib/situations.ts @@ -8,31 +8,44 @@ export interface Axis { } export interface EntitySituation { - [key: string]: { [date: string]: number | null } | string[] + [key: string]: { [date: string]: number | null } | string | string[] } export interface EntitySituationComplement { [key: string]: boolean | number | string | null } +// TODO: Obsolete export interface FamilleSituation extends EntitySituation { enfants: string[] parents: string[] } +// TODO: Obsolete export interface FoyerFiscalSituation extends EntitySituation { declarants: string[] personnes_a_charge: string[] } +// TODO: Obsolete export type IndividuSituation = EntitySituation +// TODO: Obsolete export interface MenageSituation extends EntitySituation { conjoint: string[] enfants: string[] personne_de_reference: string[] } +export interface PersonSituation extends PersonSituationWithoutId { + id: string +} + +export interface PersonSituationWithoutId extends EntitySituation { + name?: string +} + +// TODO: Obsolete export interface Situation { axes?: Axis[][] familles: { [name: string]: FamilleSituation }