Refactor person creation workflow: Introduce PersonEdit component and integrate it across Create, Person.vue, and modals for improved modularity. Update type definitions and API methods for consistency.

This commit is contained in:
2025-09-12 23:51:52 +02:00
parent 1c0ed9abc8
commit c05d0aad47
12 changed files with 692 additions and 624 deletions

View File

@@ -1,15 +1,35 @@
import {GenericDoc} from "ChillDocStoreAssets/types/generic_doc"; import { GenericDoc } from "ChillDocStoreAssets/types/generic_doc";
import {StoredObject, StoredObjectStatus} from "ChillDocStoreAssets/types"; import { StoredObject, StoredObjectStatus } from "ChillDocStoreAssets/types";
import {CreatableEntityType} from "ChillPersonAssets/types"; import { CreatableEntityType } from "ChillPersonAssets/types";
export interface DateTime { export interface DateTime {
datetime: string; datetime: string;
datetime8601: string; datetime8601: string;
} }
/**
* A date representation to use when we create an instance
*/
export interface DateTimeCreate {
/**
* Must be a string in format Y-m-d\TH:i:sO
*/
datetime: string;
}
export interface Civility { export interface Civility {
type: "chill_main_civility";
id: number; id: number;
// TODO abbreviation: TranslatableString;
active: boolean;
name: TranslatableString;
}
export interface Gender {
type: "chill_main_gender";
id: number;
label: string;
genderTranslation: string;
} }
export interface Household { export interface Household {
@@ -306,7 +326,7 @@ export interface TabDefinition {
* Configuration for the CreateModal and Create component * Configuration for the CreateModal and Create component
*/ */
export interface CreateComponentConfig { export interface CreateComponentConfig {
action?: string; action?: string;
allowedTypes: CreatableEntityType[]; allowedTypes: CreatableEntityType[];
query?: string; query?: string;
} }

View File

@@ -1,6 +1,6 @@
<template> <template>
<ul class="nav nav-tabs"> <ul class="nav nav-tabs">
<li v-if="allowedTypes.includes('person')" class="nav-item"> <li v-if="containsPerson" class="nav-item">
<a class="nav-link" :class="{ active: isActive('person') }"> <a class="nav-link" :class="{ active: isActive('person') }">
<label for="person"> <label for="person">
<input <input
@@ -14,7 +14,7 @@
</label> </label>
</a> </a>
</li> </li>
<li v-if="allowedTypes.includes('thirdparty')" class="nav-item"> <li v-if="containsThirdParty" class="nav-item">
<a class="nav-link" :class="{ active: isActive('thirdparty') }"> <a class="nav-link" :class="{ active: isActive('thirdparty') }">
<label for="thirdparty"> <label for="thirdparty">
<input <input
@@ -31,9 +31,9 @@
</ul> </ul>
<div class="my-4"> <div class="my-4">
<on-the-fly-person <PersonEdit
v-if="type === 'person'" v-if="type === 'person'"
:action="action" action="create"
:query="query" :query="query"
ref="castPerson" ref="castPerson"
/> />
@@ -47,17 +47,26 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import {computed, onMounted, ref} from "vue"; import { computed, onMounted, ref } from "vue";
import OnTheFlyPerson from "ChillPersonAssets/vuejs/_components/OnTheFly/Person.vue"; import OnTheFlyPerson from "ChillPersonAssets/vuejs/_components/OnTheFly/Person.vue";
import OnTheFlyThirdparty from "ChillThirdPartyAssets/vuejs/_components/OnTheFly/ThirdParty.vue"; import OnTheFlyThirdparty from "ChillThirdPartyAssets/vuejs/_components/OnTheFly/ThirdParty.vue";
import {ONTHEFLY_CREATE_PERSON, ONTHEFLY_CREATE_THIRDPARTY, trans,} from "translator"; import {
import {CreatableEntityType} from "ChillPersonAssets/types"; ONTHEFLY_CREATE_PERSON,
import {CreateComponentConfig} from "ChillMainAssets/types"; ONTHEFLY_CREATE_THIRDPARTY,
trans,
} from "translator";
import { CreatableEntityType } from "ChillPersonAssets/types";
import { CreateComponentConfig } from "ChillMainAssets/types";
import PersonEdit from "ChillPersonAssets/vuejs/_components/OnTheFly/PersonEdit.vue";
const props = defineProps<CreateComponentConfig>(); const props = withDefaults(defineProps<CreateComponentConfig>(), {
const type = ref<CreatableEntityType| null>(null); allowedTypes: ["person", "thirdparty"],
action: "create",
query: "",
});
const type = ref<CreatableEntityType | null>(null);
const radioType = computed<CreatableEntityType| null>({ const radioType = computed<CreatableEntityType | null>({
get: () => type.value, get: () => type.value,
set: (val: CreatableEntityType | null) => { set: (val: CreatableEntityType | null) => {
type.value = val; type.value = val;
@@ -65,7 +74,10 @@ const radioType = computed<CreatableEntityType| null>({
}, },
}); });
type AnyComponentInstance = InstanceType<typeof OnTheFlyPerson> | InstanceType<typeof OnTheFlyThirdparty> | null; type AnyComponentInstance =
| InstanceType<typeof OnTheFlyPerson>
| InstanceType<typeof OnTheFlyThirdparty>
| null;
const castPerson = ref<AnyComponentInstance>(null); const castPerson = ref<AnyComponentInstance>(null);
const castThirdparty = ref<AnyComponentInstance>(null); const castThirdparty = ref<AnyComponentInstance>(null);
@@ -81,6 +93,14 @@ function isActive(tab: CreatableEntityType) {
return type.value === tab; return type.value === tab;
} }
const containsThirdParty = computed<boolean>(() =>
props.allowedTypes.includes("thirdparty"),
);
const containsPerson = computed<boolean>(() => {
console.log(props.allowedTypes);
return props.allowedTypes.includes("person");
});
// Types for data structures coming from child components are not declared in TS yet. // Types for data structures coming from child components are not declared in TS yet.
// We conservatively type them as any to preserve runtime behavior while enabling TS in this component. // We conservatively type them as any to preserve runtime behavior while enabling TS in this component.
function castDataByType(): any { function castDataByType(): any {

View File

@@ -1,9 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import Modal from "ChillMainAssets/vuejs/_components/Modal.vue"; import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
import Create from "ChillMainAssets/vuejs/OnTheFly/components/Create.vue"; import Create from "ChillMainAssets/vuejs/OnTheFly/components/Create.vue";
import {CreateComponentConfig} from "ChillMainAssets/types"; import { CreateComponentConfig } from "ChillMainAssets/types";
const emit = defineEmits<(e: "close") => void>();
const props = defineProps<CreateComponentConfig>(); const props = defineProps<CreateComponentConfig>();
const modalDialogClass = { "modal-xl": true, "modal-scrollable": true };
</script> </script>
<template> <template>
@@ -18,15 +21,15 @@ const props = defineProps<CreateComponentConfig>();
</template> </template>
<template #body-head> <template #body-head>
<div class="modal-body"> <div class="modal-body">
<Create :allowed-types="props.allowed-types" :action="props.action" :query="props.query"></Create> <Create
:allowedTypes="props.allowedTypes"
:action="props.action"
:query="props.query"
></Create>
</div> </div>
</template> </template>
</modal>
</modal> </teleport>
</teleport>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

View File

@@ -76,7 +76,8 @@ import {
Entities, Entities,
EntitiesOrMe, EntitiesOrMe,
EntityType, EntityType,
SearchOptions, Suggestion, SearchOptions,
Suggestion,
} from "ChillPersonAssets/types"; } from "ChillPersonAssets/types";
import { import {
PICK_ENTITY_MODAL_TITLE, PICK_ENTITY_MODAL_TITLE,
@@ -183,7 +184,7 @@ function addNewSuggested(entity: EntitiesOrMe) {
emits("addNewEntity", { entity }); emits("addNewEntity", { entity });
} }
function addNewEntity({ selected }: { selected: Suggestion[]}) { function addNewEntity({ selected }: { selected: Suggestion[] }) {
Object.values(selected).forEach((item) => { Object.values(selected).forEach((item) => {
emits("addNewEntity", { entity: item.result }); emits("addNewEntity", { entity: item.result });
}); });

View File

@@ -52,23 +52,15 @@ import { trans, MODAL_ACTION_CLOSE } from "translator";
import { defineProps } from "vue"; import { defineProps } from "vue";
export interface ModalProps { export interface ModalProps {
modalDialogClass: string; modalDialogClass?: string | Record<string, boolean>;
hideFooter: boolean; hideFooter?: boolean;
show?: boolean;
} }
defineProps({ const props = withDefaults(defineProps<ModalProps>(), {
modalDialogClass: { modalDialogClass: "",
type: String, hideFooter: false,
default: "", show: true,
},
hideFooter: {
type: Boolean,
default: false,
},
show: {
type: Boolean,
default: true,
},
}); });
const emits = defineEmits<{ const emits = defineEmits<{

View File

@@ -10,6 +10,7 @@ import {
Scope, Scope,
Job, Job,
PrivateCommentEmbeddable, PrivateCommentEmbeddable,
TranslatableString,
} from "ChillMainAssets/types"; } from "ChillMainAssets/types";
import { StoredObject } from "ChillDocStoreAssets/types"; import { StoredObject } from "ChillDocStoreAssets/types";
import { Thirdparty } from "../../../ChillThirdPartyBundle/Resources/public/types"; import { Thirdparty } from "../../../ChillThirdPartyBundle/Resources/public/types";
@@ -17,7 +18,7 @@ import { Calendar } from "../../../ChillCalendarBundle/Resources/public/types";
import Person from "./vuejs/_components/OnTheFly/Person.vue"; import Person from "./vuejs/_components/OnTheFly/Person.vue";
export interface AltName { export interface AltName {
label: string; label: TranslatableString;
key: string; key: string;
} }
export interface Person { export interface Person {
@@ -331,15 +332,13 @@ export interface AccompanyingPeriodWorkEvaluationDocument {
/** /**
* Entity types that a user can create * Entity types that a user can create
*/ */
export type CreatableEntityType = export type CreatableEntityType = "person" | "thirdparty";
| "person"
| "thirdparty";
/** /**
* Entities that can be search and selected by a user * Entities that can be search and selected by a user
*/ */
export type EntityType = CreatableEntityType export type EntityType =
| CreatableEntityType
| "user_group" | "user_group"
| "user" | "user"
| "household"; | "household";

View File

@@ -1,88 +0,0 @@
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
/*
* GET a person by id
*/
const getPerson = (id) => {
const url = `/api/1.0/person/person/${id}.json`;
return fetch(url).then((response) => {
if (response.ok) {
return response.json();
}
throw Error("Error with request resource response");
});
};
const getPersonAltNames = () =>
fetch("/api/1.0/person/config/alt_names.json").then((response) => {
if (response.ok) {
return response.json();
}
throw Error("Error with request resource response");
});
const getCivilities = () =>
fetch("/api/1.0/main/civility.json").then((response) => {
if (response.ok) {
return response.json();
}
throw Error("Error with request resource response");
});
const getGenders = () => makeFetch("GET", "/api/1.0/main/gender.json");
// .then(response => {
// console.log(response)
// if (response.ok) { return response.json(); }
// throw Error('Error with request resource response');
// });
const getCentersForPersonCreation = () =>
makeFetch("GET", "/api/1.0/person/creation/authorized-centers", null);
/*
* POST a new person
*/
const postPerson = (body) => {
const url = `/api/1.0/person/person.json`;
return fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json;charset=utf-8",
},
body: JSON.stringify(body),
}).then((response) => {
if (response.ok) {
return response.json();
}
throw Error("Error with request resource response");
});
};
/*
* PATCH an existing person
*/
const patchPerson = (id, body) => {
const url = `/api/1.0/person/person/${id}.json`;
return fetch(url, {
method: "PATCH",
headers: {
"Content-Type": "application/json;charset=utf-8",
},
body: JSON.stringify(body),
}).then((response) => {
if (response.ok) {
return response.json();
}
throw Error("Error with request resource response");
});
};
export {
getCentersForPersonCreation,
getPerson,
getPersonAltNames,
getCivilities,
getGenders,
postPerson,
patchPerson,
};

View File

@@ -0,0 +1,33 @@
import { fetchResults, makeFetch } from "ChillMainAssets/lib/api/apiMethods";
import { Center, Civility, Gender } from "ChillMainAssets/types";
import { AltName, Person } from "ChillPersonAssets/types";
/*
* GET a person by id
*/
export const getPerson = async (id: number): Promise<Person> => {
const url = `/api/1.0/person/person/${id}.json`;
return fetch(url).then((response) => {
if (response.ok) {
return response.json();
}
throw Error("Error with request resource response");
});
};
export const getPersonAltNames = async (): Promise<AltName[]> =>
fetch("/api/1.0/person/config/alt_names.json").then((response) => {
if (response.ok) {
return response.json();
}
throw Error("Error with request resource response");
});
export const getCivilities = async (): Promise<Civility[]> =>
fetchResults("/api/1.0/main/civility.json");
export const getGenders = async (): Promise<Gender[]> =>
fetchResults("/api/1.0/main/gender.json");
export const getCentersForPersonCreation = async (): Promise<Center[]> =>
makeFetch("GET", "/api/1.0/person/creation/authorized-centers", null);

View File

@@ -3,7 +3,7 @@
class="btn" class="btn"
:class="getClassButton" :class="getClassButton"
:title="buttonTitle" :title="buttonTitle"
@click="openModal" @click="openModalChoose"
> >
<span v-if="displayTextButton">{{ buttonTitle }}</span> <span v-if="displayTextButton">{{ buttonTitle }}</span>
</a> </a>
@@ -16,22 +16,28 @@
:selected="selected" :selected="selected"
:modal-dialog-class="'modal-dialog-scrollable modal-xl'" :modal-dialog-class="'modal-dialog-scrollable modal-xl'"
:allow-create="props.allowCreate" :allow-create="props.allowCreate"
@close="closeModal" @close="closeModalChoose"
@addNewPersons="payload => emit('addNewPersons', payload)" @addNewPersons="(payload) => emit('addNewPersons', payload)"
@onAskForCreate="onAskForCreate" @onAskForCreate="onAskForCreate"
/> />
<CreateModal <CreateModal
v-if="creatableEntityTypes.length > 0 && showModalCreate" v-if="creatableEntityTypes.length > 0 && showModalCreate"
:allowed-types="creatableEntityTypes" :allowed-types="creatableEntityTypes"
></CreateModal> @close="closeModalCreate"
></CreateModal>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed } from 'vue'; import { ref, computed } from "vue";
import PersonChooseModal from './AddPersons/PersonChooseModal.vue'; import PersonChooseModal from "./AddPersons/PersonChooseModal.vue";
import type {Suggestion, SearchOptions, CreatableEntityType, EntityType} from 'ChillPersonAssets/types'; import type {
import {marked} from "marked"; Suggestion,
SearchOptions,
CreatableEntityType,
EntityType,
} from "ChillPersonAssets/types";
import { marked } from "marked";
import options = marked.options; import options = marked.options;
import CreateModal from "ChillMainAssets/vuejs/OnTheFly/components/CreateModal.vue"; import CreateModal from "ChillMainAssets/vuejs/OnTheFly/components/CreateModal.vue";
@@ -42,7 +48,7 @@ interface AddPersonsConfig {
modalTitle: string; modalTitle: string;
options: SearchOptions; options: SearchOptions;
allowCreate?: boolean; allowCreate?: boolean;
types?: EntityType|undefined; types?: EntityType | undefined;
} }
const props = withDefaults(defineProps<AddPersonsConfig>(), { const props = withDefaults(defineProps<AddPersonsConfig>(), {
@@ -52,43 +58,54 @@ const props = withDefaults(defineProps<AddPersonsConfig>(), {
types: () => undefined, types: () => undefined,
}); });
const emit = defineEmits<{ const emit =
(e: 'addNewPersons', payload: { selected: Suggestion[] }): void; defineEmits<
}>(); (e: "addNewPersons", payload: { selected: Suggestion[] }) => void
>();
const showModalChoose = ref(false); const showModalChoose = ref(false);
const showModalCreate = ref(false); const showModalCreate = ref(false);
const getClassButton = computed(() => { const getClassButton = computed(() => {
const size = props.options?.button?.size ?? ''; const size = props.options?.button?.size ?? "";
const type = props.options?.button?.type ?? 'btn-create'; const type = props.options?.button?.type ?? "btn-create";
return size ? `${size} ${type}` : type; return size ? `${size} ${type}` : type;
}); });
const displayTextButton = computed(() => const displayTextButton = computed(() =>
props.options?.button?.display !== undefined ? props.options.button.display : true, props.options?.button?.display !== undefined
? props.options.button.display
: true,
); );
const creatableEntityTypes = computed<CreatableEntityType[]>(() => { const creatableEntityTypes = computed<CreatableEntityType[]>(() => {
if (typeof props.options.type !== 'undefined') { if (typeof props.options.type !== "undefined") {
return props.options.type.filter((e: EntityType) => e === 'thirdparty' || e === 'person'); return props.options.type.filter(
(e: EntityType) => e === "thirdparty" || e === "person",
);
} }
return props.type.filter((e: EntityType) => e === 'thirdparty' || e === 'person'); return props.types.filter(
}) (e: EntityType) => e === "thirdparty" || e === "person",
);
});
function onAskForCreate({query}: {query: string}) { function onAskForCreate({ query }: { query: string }) {
console.log('onAskForCreate', query); console.log("onAskForCreate", query);
showModalChoose.value = false; showModalChoose.value = false;
showModalCreate.value = true; showModalCreate.value = true;
} }
function openModal() { function openModalChoose() {
showModalChoose.value = true; showModalChoose.value = true;
} }
function closeModal() { function closeModalChoose() {
showModalChoose.value = false; showModalChoose.value = false;
} }
function closeModalCreate() {
showModalCreate.value = false;
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@@ -14,7 +14,9 @@
<div class="search"> <div class="search">
<label class="col-form-label" style="float: right"> <label class="col-form-label" style="float: right">
{{ {{
trans(ADD_PERSONS_SUGGESTED_COUNTER, { count: suggestedCounter }) trans(ADD_PERSONS_SUGGESTED_COUNTER, {
count: suggestedCounter,
})
}} }}
</label> </label>
@@ -26,7 +28,11 @@
ref="searchRef" ref="searchRef"
/> />
<i class="fa fa-search fa-lg" /> <i class="fa fa-search fa-lg" />
<i class="fa fa-times" v-if="queryLength >= 3" @click="resetSuggestion" /> <i
class="fa fa-times"
v-if="queryLength >= 3"
@click="resetSuggestion"
/>
</div> </div>
</div> </div>
@@ -42,7 +48,9 @@
</a> </a>
</span> </span>
<span v-if="selectedCounter > 0"> <span v-if="selectedCounter > 0">
{{ trans(ADD_PERSONS_SELECTED_COUNTER, { count: selectedCounter }) }} {{
trans(ADD_PERSONS_SELECTED_COUNTER, { count: selectedCounter })
}}
</span> </span>
</div> </div>
</div> </div>
@@ -60,11 +68,11 @@
@update-selected="updateSelected" @update-selected="updateSelected"
/> />
<div v-if="props.allowCreate && query.length > 0" class="create-button"> <div
<button v-if="props.allowCreate && query.length > 0"
type="button" class="create-button"
@click="emit('onAskForCreate', {query })" >
> <button type="button" @click="emit('onAskForCreate', { query })">
{{ trans(ONTHEFLY_CREATE_BUTTON, { q: query }) }} {{ trans(ONTHEFLY_CREATE_BUTTON, { q: query }) }}
</button> </button>
<!-- <!--
@@ -100,12 +108,12 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import {ref, reactive, computed, nextTick, watch, onMounted} from 'vue'; import { ref, reactive, computed, nextTick, watch, onMounted } from "vue";
import Modal from 'ChillMainAssets/vuejs/_components/Modal.vue'; import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
import PersonSuggestion from './PersonSuggestion.vue'; import PersonSuggestion from "./PersonSuggestion.vue";
import OnTheFly from 'ChillMainAssets/vuejs/OnTheFly/components/OnTheFly.vue'; import OnTheFly from "ChillMainAssets/vuejs/OnTheFly/components/OnTheFly.vue";
import { searchEntities } from 'ChillPersonAssets/vuejs/_api/AddPersons'; import { searchEntities } from "ChillPersonAssets/vuejs/_api/AddPersons";
import { makeFetch } from 'ChillMainAssets/lib/api/apiMethods'; import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
import { import {
trans, trans,
@@ -116,14 +124,15 @@ import {
ACTION_CHECK_ALL, ACTION_CHECK_ALL,
ACTION_RESET, ACTION_RESET,
ACTION_ADD, ACTION_ADD,
} from 'translator'; } from "translator";
import type { import type {
Suggestion, Suggestion,
Search, Search,
AddPersonResult as OriginalResult, AddPersonResult as OriginalResult,
SearchOptions, EntitiesOrMe, SearchOptions,
} from 'ChillPersonAssets/types'; EntitiesOrMe,
} from "ChillPersonAssets/types";
type Result = OriginalResult & { addressId?: number }; type Result = OriginalResult & { addressId?: number };
@@ -139,16 +148,16 @@ interface Props {
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
suggested: () => [], suggested: () => [],
selected: () => [], selected: () => [],
modalDialogClass: 'modal-dialog-scrollable modal-xl', modalDialogClass: "modal-dialog-scrollable modal-xl",
allowCreate: () => true, allowCreate: () => true,
}); });
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'close'): void; (e: "close"): void;
/** @deprecated use 'onPickEntities' */ /** @deprecated use 'onPickEntities' */
(e: 'addNewPersons', payload: { selected: Suggestion[] }): void; (e: "addNewPersons", payload: { selected: Suggestion[] }): void;
(e: 'onPickEntities', payload: { selected: EntitiesOrMe[] }): void; (e: "onPickEntities", payload: { selected: EntitiesOrMe[] }): void;
(e: 'onAskForCreate', payload: { query: string }): void; (e: "onAskForCreate", payload: { query: string }): void;
}>(); }>();
const searchRef = ref<HTMLInputElement | null>(null); const searchRef = ref<HTMLInputElement | null>(null);
@@ -160,8 +169,8 @@ onMounted(() => {
}); });
const search = reactive({ const search = reactive({
query: '' as string, query: "" as string,
previousQuery: '' as string, previousQuery: "" as string,
currentSearchQueryController: null as AbortController | null, currentSearchQueryController: null as AbortController | null,
suggested: (props.suggested ?? []) as Suggestion[], suggested: (props.suggested ?? []) as Suggestion[],
selected: (props.selected ?? []) as Suggestion[], selected: (props.selected ?? []) as Suggestion[],
@@ -173,7 +182,7 @@ watch(
(newSelected) => { (newSelected) => {
search.selected = newSelected ? [...newSelected] : []; search.selected = newSelected ? [...newSelected] : [];
}, },
{ deep: true } { deep: true },
); );
watch( watch(
@@ -181,7 +190,7 @@ watch(
(newSuggested) => { (newSuggested) => {
search.suggested = newSuggested ? [...newSuggested] : []; search.suggested = newSuggested ? [...newSuggested] : [];
}, },
{ deep: true } { deep: true },
); );
const query = computed({ const query = computed({
@@ -193,7 +202,9 @@ const suggestedCounter = computed(() => search.suggested.length);
const selectedComputed = computed<Suggestion[]>(() => search.selected); const selectedComputed = computed<Suggestion[]>(() => search.selected);
const selectedCounter = computed(() => search.selected.length); const selectedCounter = computed(() => search.selected.length);
const checkUniq = computed(() => (props.options.uniq === true ? 'radio' : 'checkbox')); const checkUniq = computed(() =>
props.options.uniq === true ? "radio" : "checkbox",
);
const priorSuggestion = computed(() => search.priorSuggestion); const priorSuggestion = computed(() => search.priorSuggestion);
const hasPriorSuggestion = computed(() => !!search.priorSuggestion.key); const hasPriorSuggestion = computed(() => !!search.priorSuggestion.key);
@@ -230,7 +241,7 @@ function setQuery(q: string) {
search.currentSearchQueryController = null; search.currentSearchQueryController = null;
} }
if (q === '') { if (q === "") {
loadSuggestions([]); loadSuggestions([]);
return; return;
} }
@@ -250,7 +261,7 @@ function setQuery(q: string) {
loadSuggestions(suggested.results); loadSuggestions(suggested.results);
}) })
.catch((error: DOMException) => { .catch((error: DOMException) => {
if (error instanceof DOMException && error.name === 'AbortError') { if (error instanceof DOMException && error.name === "AbortError") {
return; return;
} }
throw error; throw error;
@@ -270,7 +281,7 @@ function updateSelected(value: Suggestion[]) {
} }
function resetSuggestion() { function resetSuggestion() {
search.query = ''; search.query = "";
search.suggested = []; search.suggested = [];
} }
@@ -306,10 +317,12 @@ function newPriorSuggestion(entity: Result | null) {
* Triggered when the user clicks on the "add" button. * Triggered when the user clicks on the "add" button.
*/ */
function pickEntities(): void { function pickEntities(): void {
emit('addNewPersons', { selected: search.selected }); emit("addNewPersons", { selected: search.selected });
emit('onPickEntities', {selected: search.selected.map((s: Suggestion) => s.result )}) emit("onPickEntities", {
search.query = ''; selected: search.selected.map((s: Suggestion) => s.result),
emit('close'); });
search.query = "";
emit("close");
} }
/* /*

View File

@@ -1,5 +1,5 @@
<template> <template>
<div v-if="action === 'show'"> <div v-if="action === 'show' && person !== null">
<div class="flex-table"> <div class="flex-table">
<person-render-box <person-render-box
render="bloc" render="bloc"
@@ -22,445 +22,48 @@
</div> </div>
<div v-else-if="action === 'edit' || action === 'create'"> <div v-else-if="action === 'edit' || action === 'create'">
<div class="form-floating mb-3"> <PersonEdit
<input :id="props.id"
class="form-control form-control-lg" :type="props.type"
id="lastname" :action="props.action"
v-model="lastName" :query="props.query"
:placeholder="trans(PERSON_MESSAGES_PERSON_LASTNAME)" />
@change="checkErrors"
/>
<label for="lastname">{{ trans(PERSON_MESSAGES_PERSON_LASTNAME) }}</label>
</div>
<div v-if="queryItems">
<ul class="list-suggest add-items inline">
<li
v-for="(qi, i) in queryItems"
:key="i"
@click="addQueryItem('lastName', qi)"
>
<span class="person-text">{{ qi }}</span>
</li>
</ul>
</div>
<div class="form-floating mb-3">
<input
class="form-control form-control-lg"
id="firstname"
v-model="firstName"
:placeholder="trans(PERSON_MESSAGES_PERSON_FIRSTNAME)"
@change="checkErrors"
/>
<label for="firstname">{{
trans(PERSON_MESSAGES_PERSON_FIRSTNAME)
}}</label>
</div>
<div v-if="queryItems">
<ul class="list-suggest add-items inline">
<li
v-for="(qi, i) in queryItems"
:key="i"
@click="addQueryItem('firstName', qi)"
>
<span class="person-text">{{ qi }}</span>
</li>
</ul>
</div>
<div
v-for="(a, i) in config.altNames"
:key="a.key"
class="form-floating mb-3"
>
<input
class="form-control form-control-lg"
:id="a.key"
:value="personAltNamesLabels[i]"
@input="onAltNameInput"
/>
<label :for="a.key">{{ localizeString(a.labels) }}</label>
</div>
<!-- TODO fix placeholder if undefined
-->
<div class="form-floating mb-3">
<select class="form-select form-select-lg" id="gender" v-model="gender">
<option selected disabled>
{{ trans(PERSON_MESSAGES_PERSON_GENDER_PLACEHOLDER) }}
</option>
<option v-for="g in config.genders" :value="g.id" :key="g.id">
{{ g.label }}
</option>
</select>
<label>{{ trans(PERSON_MESSAGES_PERSON_GENDER_TITLE) }}</label>
</div>
<div
class="form-floating mb-3"
v-if="showCenters && config.centers.length > 1"
>
<select class="form-select form-select-lg" id="center" v-model="center">
<option selected disabled>
{{ trans(PERSON_MESSAGES_PERSON_CENTER_PLACEHOLDER) }}
</option>
<option v-for="c in config.centers" :value="c" :key="c.id">
{{ c.name }}
</option>
</select>
<label>{{ trans(PERSON_MESSAGES_PERSON_CENTER_TITLE) }}</label>
</div>
<div class="form-floating mb-3">
<select
class="form-select form-select-lg"
id="civility"
v-model="civility"
>
<option selected disabled>
{{ trans(PERSON_MESSAGES_PERSON_CIVILITY_PLACEHOLDER) }}
</option>
<option v-for="c in config.civilities" :value="c.id" :key="c.id">
{{ localizeString(c.name) }}
</option>
</select>
<label>{{ trans(PERSON_MESSAGES_PERSON_CIVILITY_TITLE) }}</label>
</div>
<div class="input-group mb-3">
<span class="input-group-text" id="phonenumber">
<i class="fa fa-fw fa-phone"></i>
</span>
<input
class="form-control form-control-lg"
v-model="phonenumber"
:placeholder="trans(PERSON_MESSAGES_PERSON_PHONENUMBER)"
:aria-label="trans(PERSON_MESSAGES_PERSON_PHONENUMBER)"
aria-describedby="phonenumber"
/>
</div>
<div class="input-group mb-3">
<span class="input-group-text" id="mobilenumber">
<i class="fa fa-fw fa-mobile"></i>
</span>
<input
class="form-control form-control-lg"
v-model="mobilenumber"
:placeholder="trans(PERSON_MESSAGES_PERSON_MOBILENUMBER)"
:aria-label="trans(PERSON_MESSAGES_PERSON_MOBILENUMBER)"
aria-describedby="mobilenumber"
/>
</div>
<div class="input-group mb-3">
<span class="input-group-text" id="email">
<i class="fa fa-fw fa-at"></i>
</span>
<input
class="form-control form-control-lg"
v-model="email"
:placeholder="trans(PERSON_MESSAGES_PERSON_EMAIL)"
:aria-label="trans(PERSON_MESSAGES_PERSON_EMAIL)"
aria-describedby="email"
/>
</div>
<div v-if="action === 'create'" class="input-group mb-3 form-check">
<input
class="form-check-input"
type="checkbox"
v-model="showAddressForm"
name="showAddressForm"
/>
<label class="form-check-label">
{{ trans(PERSON_MESSAGES_PERSON_ADDRESS_SHOW_ADDRESS_FORM) }}
</label>
</div>
<div
v-if="action === 'create' && showAddressFormValue"
class="form-floating mb-3"
>
<p>{{ trans(PERSON_MESSAGES_PERSON_ADDRESS_WARNING) }}</p>
<AddAddress
:context="addAddress.context"
:options="addAddress.options"
:addressChangedCallback="submitNewAddress"
ref="addAddress"
/>
</div>
<div class="alert alert-warning" v-if="errors.length">
<ul>
<li v-for="(e, i) in errors" :key="i">{{ e }}</li>
</ul>
</div>
</div> </div>
</template> </template>
<script setup> <script setup lang="ts">
import { ref, reactive, computed, onMounted } from "vue"; import { ref, onMounted } from "vue";
import { import { getPerson } from "../../_api/OnTheFly";
getCentersForPersonCreation,
getCivilities,
getGenders,
getPerson,
getPersonAltNames,
} from "../../_api/OnTheFly";
import PersonRenderBox from "../Entity/PersonRenderBox.vue"; import PersonRenderBox from "../Entity/PersonRenderBox.vue";
import AddAddress from "ChillMainAssets/vuejs/Address/components/AddAddress.vue"; import PersonEdit from "./PersonEdit.vue";
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper"; import type { Person } from "ChillPersonAssets/types";
import {
trans,
PERSON_MESSAGES_PERSON_LASTNAME,
PERSON_MESSAGES_PERSON_FIRSTNAME,
PERSON_MESSAGES_PERSON_GENDER_PLACEHOLDER,
PERSON_MESSAGES_PERSON_GENDER_TITLE,
PERSON_MESSAGES_PERSON_CENTER_PLACEHOLDER,
PERSON_MESSAGES_PERSON_CENTER_TITLE,
PERSON_MESSAGES_PERSON_CIVILITY_PLACEHOLDER,
PERSON_MESSAGES_PERSON_CIVILITY_TITLE,
PERSON_MESSAGES_PERSON_PHONENUMBER,
PERSON_MESSAGES_PERSON_MOBILENUMBER,
PERSON_MESSAGES_PERSON_EMAIL,
PERSON_MESSAGES_PERSON_ADDRESS_SHOW_ADDRESS_FORM,
PERSON_MESSAGES_PERSON_ADDRESS_WARNING,
} from "translator";
const props = defineProps({ interface Props {
id: [String, Number], id: string | number;
type: String, type?: string;
action: String, action: "show" | "edit" | "create";
query: String, query?: string;
});
const person = reactive({
type: "person",
lastName: "",
firstName: "",
altNames: [],
addressId: null,
center: null,
gender: null,
civility: null,
birthdate: null,
phonenumber: "",
mobilenumber: "",
email: "",
});
const config = reactive({
altNames: [],
civilities: [],
centers: [],
genders: [],
});
const showCenters = ref(false);
const showAddressFormValue = ref(false);
const errors = ref([]);
const addAddress = reactive({
options: {
button: {
text: { create: "person.address.create_address" },
size: "btn-sm",
},
title: { create: "person.address.create_address" },
},
context: {
target: {},
edit: false,
addressId: null,
defaults: window.addaddress,
},
});
const firstName = computed({
get: () => person.firstName,
set: (value) => {
person.firstName = value;
},
});
const lastName = computed({
get: () => person.lastName,
set: (value) => {
person.lastName = value;
},
});
const gender = computed({
get: () => (person.gender ? person.gender.id : null),
set: (value) => {
person.gender = { id: value, type: "chill_main_gender" };
},
});
const civility = computed({
get: () => (person.civility ? person.civility.id : null),
set: (value) => {
person.civility = { id: value, type: "chill_main_civility" };
},
});
const birthDate = computed({
get: () => (person.birthdate ? person.birthdate.datetime.split("T")[0] : ""),
set: (value) => {
if (person.birthdate) {
person.birthdate.datetime = value + "T00:00:00+0100";
} else {
person.birthdate = { datetime: value + "T00:00:00+0100" };
}
},
});
const phonenumber = computed({
get: () => person.phonenumber,
set: (value) => {
person.phonenumber = value;
},
});
const mobilenumber = computed({
get: () => person.mobilenumber,
set: (value) => {
person.mobilenumber = value;
},
});
const email = computed({
get: () => person.email,
set: (value) => {
person.email = value;
},
});
const showAddressForm = computed({
get: () => showAddressFormValue.value,
set: (value) => {
showAddressFormValue.value = value;
},
});
const center = computed({
get: () => {
const c = config.centers.find(
(c) => person.center !== null && person.center.id === c.id,
);
return typeof c === "undefined" ? null : c;
},
set: (value) => {
person.center = { id: value.id, type: value.type };
},
});
const genderClass = computed(() => {
switch (person.gender && person.gender.id) {
case "woman":
return "fa-venus";
case "man":
return "fa-mars";
case "both":
return "fa-neuter";
case "unknown":
return "fa-genderless";
default:
return "fa-genderless";
}
});
const genderTranslation = computed(() => {
switch (person.gender && person.gender.genderTranslation) {
case "woman":
return PERSON_MESSAGES_PERSON_GENDER_WOMAN;
case "man":
return PERSON_MESSAGES_PERSON_GENDER_MAN;
case "neutral":
return PERSON_MESSAGES_PERSON_GENDER_NEUTRAL;
case "unknown":
return PERSON_MESSAGES_PERSON_GENDER_UNKNOWN;
default:
return PERSON_MESSAGES_PERSON_GENDER_UNKNOWN;
}
});
const feminized = computed(() =>
person.gender && person.gender.id === "woman" ? "e" : "",
);
const personAltNamesLabels = computed(() =>
person.altNames.map((a) => (a ? a.label : "")),
);
const queryItems = computed(() =>
props.query ? props.query.split(" ") : null,
);
function checkErrors() {
errors.value = [];
if (person.lastName === "") {
errors.value.push("Le nom ne doit pas être vide.");
}
if (person.firstName === "") {
errors.value.push("Le prénom ne doit pas être vide.");
}
if (!person.gender) {
errors.value.push("Le genre doit être renseigné");
}
if (showCenters.value && person.center === null) {
errors.value.push("Le centre doit être renseigné");
}
} }
function loadData() { const props = defineProps<Props>();
getPerson(props.id).then((p) => {
Object.assign(person, p); const person = ref<Person | null>(null);
function loadData(): void {
if (props.id === undefined || props.id === null) {
return;
}
const idNum = typeof props.id === "string" ? Number(props.id) : props.id;
if (!Number.isFinite(idNum)) {
return;
}
getPerson(idNum as number).then((p) => {
person.value = p;
}); });
} }
function onAltNameInput(event) {
const key = event.target.id;
const label = event.target.value;
let updateAltNames = person.altNames.filter((a) => a.key !== key);
updateAltNames.push({ key: key, label: label });
person.altNames = updateAltNames;
}
function addQueryItem(field, queryItem) {
switch (field) {
case "lastName":
person.lastName = person.lastName
? (person.lastName += ` ${queryItem}`)
: queryItem;
break;
case "firstName":
person.firstName = person.firstName
? (person.firstName += ` ${queryItem}`)
: queryItem;
break;
}
}
function submitNewAddress(payload) {
person.addressId = payload.addressId;
}
onMounted(() => { onMounted(() => {
getPersonAltNames().then((altNames) => {
config.altNames = altNames;
});
getCivilities().then((civilities) => {
if ("results" in civilities) {
config.civilities = civilities.results;
}
});
getGenders().then((genders) => {
if ("results" in genders) {
config.genders = genders.results;
}
});
if (props.action !== "create") { if (props.action !== "create") {
loadData(); loadData();
} else {
getCentersForPersonCreation().then((params) => {
config.centers = params.centers.filter((c) => c.isActive);
showCenters.value = params.showCenters;
if (showCenters.value && config.centers.length === 1) {
person.center = config.centers[0];
}
});
} }
}); });
defineExpose(genderClass, genderTranslation, feminized, birthDate);
</script> </script>

View File

@@ -0,0 +1,455 @@
<template>
<div>
<div class="form-floating mb-3">
<input
class="form-control form-control-lg"
id="lastname"
v-model="lastName"
:placeholder="trans(PERSON_MESSAGES_PERSON_LASTNAME)"
@change="checkErrors"
/>
<label for="lastname">{{ trans(PERSON_MESSAGES_PERSON_LASTNAME) }}</label>
</div>
<div v-if="queryItems">
<ul class="list-suggest add-items inline">
<li
v-for="(qi, i) in queryItems"
:key="i"
@click="addQueryItem('lastName', qi)"
>
<span class="person-text">{{ qi }}</span>
</li>
</ul>
</div>
<div class="form-floating mb-3">
<input
class="form-control form-control-lg"
id="firstname"
v-model="firstName"
:placeholder="trans(PERSON_MESSAGES_PERSON_FIRSTNAME)"
@change="checkErrors"
/>
<label for="firstname">{{
trans(PERSON_MESSAGES_PERSON_FIRSTNAME)
}}</label>
</div>
<div v-if="queryItems">
<ul class="list-suggest add-items inline">
<li
v-for="(qi, i) in queryItems"
:key="i"
@click="addQueryItem('firstName', qi)"
>
<span class="person-text">{{ qi }}</span>
</li>
</ul>
</div>
<div
v-for="(a, i) in config.altNames"
:key="a.key"
class="form-floating mb-3"
>
<input
class="form-control form-control-lg"
:id="a.key"
:value="personAltNamesLabels[i]"
@input="onAltNameInput"
/>
<label :for="a.key">{{ localizeString(a.labels) }}</label>
</div>
<div class="form-floating mb-3">
<select class="form-select form-select-lg" id="gender" v-model="gender">
<option selected disabled>
{{ trans(PERSON_MESSAGES_PERSON_GENDER_PLACEHOLDER) }}
</option>
<option v-for="g in config.genders" :value="g.id" :key="g.id">
{{ g.label }}
</option>
</select>
<label>{{ trans(PERSON_MESSAGES_PERSON_GENDER_TITLE) }}</label>
</div>
<div
class="form-floating mb-3"
v-if="showCenters && config.centers.length > 1"
>
<select class="form-select form-select-lg" id="center" v-model="center">
<option selected disabled>
{{ trans(PERSON_MESSAGES_PERSON_CENTER_PLACEHOLDER) }}
</option>
<option v-for="c in config.centers" :value="c" :key="c.id">
{{ c.name }}
</option>
</select>
<label>{{ trans(PERSON_MESSAGES_PERSON_CENTER_TITLE) }}</label>
</div>
<div class="form-floating mb-3">
<select
class="form-select form-select-lg"
id="civility"
v-model="civility"
>
<option selected disabled>
{{ trans(PERSON_MESSAGES_PERSON_CIVILITY_PLACEHOLDER) }}
</option>
<option v-for="c in config.civilities" :value="c.id" :key="c.id">
{{ localizeString(c.name) }}
</option>
</select>
<label>{{ trans(PERSON_MESSAGES_PERSON_CIVILITY_TITLE) }}</label>
</div>
<div class="input-group mb-3">
<span class="input-group-text" id="phonenumber">
<i class="fa fa-fw fa-phone"></i>
</span>
<input
class="form-control form-control-lg"
v-model="phonenumber"
:placeholder="trans(PERSON_MESSAGES_PERSON_PHONENUMBER)"
:aria-label="trans(PERSON_MESSAGES_PERSON_PHONENUMBER)"
aria-describedby="phonenumber"
/>
</div>
<div class="input-group mb-3">
<span class="input-group-text" id="mobilenumber">
<i class="fa fa-fw fa-mobile"></i>
</span>
<input
class="form-control form-control-lg"
v-model="mobilenumber"
:placeholder="trans(PERSON_MESSAGES_PERSON_MOBILENUMBER)"
:aria-label="trans(PERSON_MESSAGES_PERSON_MOBILENUMBER)"
aria-describedby="mobilenumber"
/>
</div>
<div class="input-group mb-3">
<span class="input-group-text" id="email">
<i class="fa fa-fw fa-at"></i>
</span>
<input
class="form-control form-control-lg"
v-model="email"
:placeholder="trans(PERSON_MESSAGES_PERSON_EMAIL)"
:aria-label="trans(PERSON_MESSAGES_PERSON_EMAIL)"
aria-describedby="email"
/>
</div>
<div v-if="action === 'create'" class="input-group mb-3 form-check">
<input
class="form-check-input"
type="checkbox"
v-model="showAddressForm"
name="showAddressForm"
/>
<label class="form-check-label">
{{ trans(PERSON_MESSAGES_PERSON_ADDRESS_SHOW_ADDRESS_FORM) }}
</label>
</div>
<div
v-if="action === 'create' && showAddressFormValue"
class="form-floating mb-3"
>
<p>{{ trans(PERSON_MESSAGES_PERSON_ADDRESS_WARNING) }}</p>
<AddAddress
:context="addAddress.context"
:options="addAddress.options"
:addressChangedCallback="submitNewAddress"
ref="addAddress"
/>
</div>
<div class="alert alert-warning" v-if="errors.length">
<ul>
<li v-for="(e, i) in errors" :key="i">{{ e }}</li>
</ul>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from "vue";
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
import AddAddress from "ChillMainAssets/vuejs/Address/components/AddAddress.vue";
import {
getCentersForPersonCreation,
getCivilities,
getGenders,
getPerson,
getPersonAltNames,
} from "../../_api/OnTheFly";
import {
trans,
PERSON_MESSAGES_PERSON_LASTNAME,
PERSON_MESSAGES_PERSON_FIRSTNAME,
PERSON_MESSAGES_PERSON_GENDER_PLACEHOLDER,
PERSON_MESSAGES_PERSON_GENDER_TITLE,
PERSON_MESSAGES_PERSON_CENTER_PLACEHOLDER,
PERSON_MESSAGES_PERSON_CENTER_TITLE,
PERSON_MESSAGES_PERSON_CIVILITY_PLACEHOLDER,
PERSON_MESSAGES_PERSON_CIVILITY_TITLE,
PERSON_MESSAGES_PERSON_PHONENUMBER,
PERSON_MESSAGES_PERSON_MOBILENUMBER,
PERSON_MESSAGES_PERSON_EMAIL,
PERSON_MESSAGES_PERSON_ADDRESS_SHOW_ADDRESS_FORM,
PERSON_MESSAGES_PERSON_ADDRESS_WARNING,
} from "translator";
import {
Center,
Civility,
Gender,
DateTimeCreate,
} from "ChillMainAssets/types";
import { AltName } from "ChillPersonAssets/types";
interface PersonState {
type: "person";
lastName: string;
firstName: string;
altNames: { key: string; label: string }[];
addressId: number | null;
center: { id: number; type: string } | null;
gender: { id: string; type: string } | null;
civility: { id: number; type: string } | null;
birthdate: DateTimeCreate | null;
phonenumber: string;
mobilenumber: string;
email: string;
}
interface PersonEditComponentConfig {
id?: number | null;
type?: string;
action: "edit" | "create";
query: string;
}
const props = withDefaults(defineProps<PersonEditComponentConfig>(), {
id: null,
type: "TODO",
});
const person = reactive<PersonState>({
type: "person",
lastName: "",
firstName: "",
altNames: [],
addressId: null,
center: null,
gender: null,
civility: null,
birthdate: null,
phonenumber: "",
mobilenumber: "",
email: "",
});
const config = reactive<{
altNames: AltName[];
civilities: Civility[];
centers: Center[];
genders: Gender[];
}>({
altNames: [],
civilities: [],
centers: [],
genders: [],
});
const showCenters = ref(false);
const showAddressFormValue = ref(false);
const errors = ref<string[]>([]);
const addAddress = reactive({
options: {
button: {
text: { create: "person.address.create_address" },
size: "btn-sm",
},
title: { create: "person.address.create_address" },
},
context: {
target: {},
edit: false,
addressId: null as number | null,
defaults: (window as any).addaddress,
},
});
const firstName = computed({
get: () => person.firstName,
set: (value: string) => {
person.firstName = value;
},
});
const lastName = computed({
get: () => person.lastName,
set: (value: string) => {
person.lastName = value;
},
});
const gender = computed({
get: () => (person.gender ? person.gender.id : null),
set: (value: string | null) => {
person.gender = value ? { id: value, type: "chill_main_gender" } : null;
},
});
const civility = computed({
get: () => (person.civility ? person.civility.id : null),
set: (value: number | null) => {
person.civility =
value !== null ? { id: value, type: "chill_main_civility" } : null;
},
});
const birthDate = computed({
get: () => (person.birthdate ? person.birthdate.datetime.split("T")[0] : ""),
set: (value: string) => {
if (person.birthdate) {
person.birthdate.datetime = value + "T00:00:00+0100";
} else {
person.birthdate = { datetime: value + "T00:00:00+0100" };
}
},
});
const phonenumber = computed({
get: () => person.phonenumber,
set: (value: string) => {
person.phonenumber = value;
},
});
const mobilenumber = computed({
get: () => person.mobilenumber,
set: (value: string) => {
person.mobilenumber = value;
},
});
const email = computed({
get: () => person.email,
set: (value: string) => {
person.email = value;
},
});
const showAddressForm = computed({
get: () => showAddressFormValue.value,
set: (value: boolean) => {
showAddressFormValue.value = value;
},
});
const center = computed({
get: () => {
const c = config.centers.find(
(c) => person.center !== null && person.center.id === c.id,
);
return typeof c === "undefined" ? null : c;
},
set: (value: Center | null) => {
if (value) {
person.center = {
id: value.id,
type: (value as any).type ?? "chill_main_center",
};
} else {
person.center = null;
}
},
});
const personAltNamesLabels = computed(() =>
person.altNames.map((a) => (a ? a.label : "")),
);
const queryItems = computed(() =>
props.query ? props.query.split(" ") : null,
);
function checkErrors() {
errors.value = [];
if (person.lastName === "") {
errors.value.push("Le nom ne doit pas être vide.");
}
if (person.firstName === "") {
errors.value.push("Le prénom ne doit pas être vide.");
}
if (!person.gender) {
errors.value.push("Le genre doit être renseigné");
}
if (showCenters.value && person.center === null) {
errors.value.push("Le centre doit être renseigné");
}
}
function loadData() {
if (props.id !== undefined && props.id !== null) {
getPerson(props.id as any).then((p: any) => {
Object.assign(person, p);
});
}
}
function onAltNameInput(event: Event) {
const target = event.target as HTMLInputElement;
const key = target.id;
const label = target.value;
const updateAltNames = person.altNames.filter((a) => a.key !== key);
updateAltNames.push({ key, label });
person.altNames = updateAltNames;
}
function addQueryItem(field: "lastName" | "firstName", queryItem: string) {
switch (field) {
case "lastName":
person.lastName = person.lastName
? (person.lastName += ` ${queryItem}`)
: queryItem;
break;
case "firstName":
person.firstName = person.firstName
? (person.firstName += ` ${queryItem}`)
: queryItem;
break;
}
}
function submitNewAddress(payload: { addressId: number }) {
person.addressId = payload.addressId;
}
onMounted(() => {
getPersonAltNames().then((altNames: any) => {
config.altNames = altNames;
});
getCivilities().then((civilities: any) => {
if ("results" in civilities) {
config.civilities = civilities.results;
}
});
getGenders().then((genders: any) => {
if ("results" in genders) {
config.genders = genders.results;
}
});
if (props.action !== "create") {
loadData();
} else {
getCentersForPersonCreation().then((params: any) => {
config.centers = params.centers.filter((c: any) => c.isActive);
showCenters.value = params.showCenters;
if (showCenters.value && config.centers.length === 1) {
// if there is only one center, preselect it
person.center = {
id: config.centers[0].id,
type: (config.centers[0] as any).type ?? "chill_main_center",
};
}
});
}
});
</script>