mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-09-25 08:05:00 +00:00
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:
@@ -1,15 +1,35 @@
|
||||
import {GenericDoc} from "ChillDocStoreAssets/types/generic_doc";
|
||||
import {StoredObject, StoredObjectStatus} from "ChillDocStoreAssets/types";
|
||||
import {CreatableEntityType} from "ChillPersonAssets/types";
|
||||
import { GenericDoc } from "ChillDocStoreAssets/types/generic_doc";
|
||||
import { StoredObject, StoredObjectStatus } from "ChillDocStoreAssets/types";
|
||||
import { CreatableEntityType } from "ChillPersonAssets/types";
|
||||
|
||||
export interface DateTime {
|
||||
datetime: 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 {
|
||||
type: "chill_main_civility";
|
||||
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 {
|
||||
@@ -306,7 +326,7 @@ export interface TabDefinition {
|
||||
* Configuration for the CreateModal and Create component
|
||||
*/
|
||||
export interface CreateComponentConfig {
|
||||
action?: string;
|
||||
allowedTypes: CreatableEntityType[];
|
||||
query?: string;
|
||||
action?: string;
|
||||
allowedTypes: CreatableEntityType[];
|
||||
query?: string;
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<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') }">
|
||||
<label for="person">
|
||||
<input
|
||||
@@ -14,7 +14,7 @@
|
||||
</label>
|
||||
</a>
|
||||
</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') }">
|
||||
<label for="thirdparty">
|
||||
<input
|
||||
@@ -31,9 +31,9 @@
|
||||
</ul>
|
||||
|
||||
<div class="my-4">
|
||||
<on-the-fly-person
|
||||
<PersonEdit
|
||||
v-if="type === 'person'"
|
||||
:action="action"
|
||||
action="create"
|
||||
:query="query"
|
||||
ref="castPerson"
|
||||
/>
|
||||
@@ -47,17 +47,26 @@
|
||||
</div>
|
||||
</template>
|
||||
<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 OnTheFlyThirdparty from "ChillThirdPartyAssets/vuejs/_components/OnTheFly/ThirdParty.vue";
|
||||
import {ONTHEFLY_CREATE_PERSON, ONTHEFLY_CREATE_THIRDPARTY, trans,} from "translator";
|
||||
import {CreatableEntityType} from "ChillPersonAssets/types";
|
||||
import {CreateComponentConfig} from "ChillMainAssets/types";
|
||||
import {
|
||||
ONTHEFLY_CREATE_PERSON,
|
||||
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 type = ref<CreatableEntityType| null>(null);
|
||||
const props = withDefaults(defineProps<CreateComponentConfig>(), {
|
||||
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,
|
||||
set: (val: CreatableEntityType | null) => {
|
||||
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 castThirdparty = ref<AnyComponentInstance>(null);
|
||||
@@ -81,6 +93,14 @@ function isActive(tab: CreatableEntityType) {
|
||||
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.
|
||||
// We conservatively type them as any to preserve runtime behavior while enabling TS in this component.
|
||||
function castDataByType(): any {
|
||||
|
@@ -1,9 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import Modal from "ChillMainAssets/vuejs/_components/Modal.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 modalDialogClass = { "modal-xl": true, "modal-scrollable": true };
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -18,15 +21,15 @@ const props = defineProps<CreateComponentConfig>();
|
||||
</template>
|
||||
<template #body-head>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
</modal>
|
||||
</teleport>
|
||||
|
||||
</modal>
|
||||
</teleport>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
||||
<style scoped lang="scss"></style>
|
||||
|
@@ -76,7 +76,8 @@ import {
|
||||
Entities,
|
||||
EntitiesOrMe,
|
||||
EntityType,
|
||||
SearchOptions, Suggestion,
|
||||
SearchOptions,
|
||||
Suggestion,
|
||||
} from "ChillPersonAssets/types";
|
||||
import {
|
||||
PICK_ENTITY_MODAL_TITLE,
|
||||
@@ -183,7 +184,7 @@ function addNewSuggested(entity: EntitiesOrMe) {
|
||||
emits("addNewEntity", { entity });
|
||||
}
|
||||
|
||||
function addNewEntity({ selected }: { selected: Suggestion[]}) {
|
||||
function addNewEntity({ selected }: { selected: Suggestion[] }) {
|
||||
Object.values(selected).forEach((item) => {
|
||||
emits("addNewEntity", { entity: item.result });
|
||||
});
|
||||
|
@@ -52,23 +52,15 @@ import { trans, MODAL_ACTION_CLOSE } from "translator";
|
||||
import { defineProps } from "vue";
|
||||
|
||||
export interface ModalProps {
|
||||
modalDialogClass: string;
|
||||
hideFooter: boolean;
|
||||
modalDialogClass?: string | Record<string, boolean>;
|
||||
hideFooter?: boolean;
|
||||
show?: boolean;
|
||||
}
|
||||
|
||||
defineProps({
|
||||
modalDialogClass: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
hideFooter: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
const props = withDefaults(defineProps<ModalProps>(), {
|
||||
modalDialogClass: "",
|
||||
hideFooter: false,
|
||||
show: true,
|
||||
});
|
||||
|
||||
const emits = defineEmits<{
|
||||
|
@@ -10,6 +10,7 @@ import {
|
||||
Scope,
|
||||
Job,
|
||||
PrivateCommentEmbeddable,
|
||||
TranslatableString,
|
||||
} from "ChillMainAssets/types";
|
||||
import { StoredObject } from "ChillDocStoreAssets/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";
|
||||
|
||||
export interface AltName {
|
||||
label: string;
|
||||
label: TranslatableString;
|
||||
key: string;
|
||||
}
|
||||
export interface Person {
|
||||
@@ -331,15 +332,13 @@ export interface AccompanyingPeriodWorkEvaluationDocument {
|
||||
/**
|
||||
* Entity types that a user can create
|
||||
*/
|
||||
export type CreatableEntityType =
|
||||
| "person"
|
||||
| "thirdparty";
|
||||
|
||||
export type CreatableEntityType = "person" | "thirdparty";
|
||||
|
||||
/**
|
||||
* Entities that can be search and selected by a user
|
||||
*/
|
||||
export type EntityType = CreatableEntityType
|
||||
export type EntityType =
|
||||
| CreatableEntityType
|
||||
| "user_group"
|
||||
| "user"
|
||||
| "household";
|
||||
|
@@ -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,
|
||||
};
|
@@ -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);
|
@@ -3,7 +3,7 @@
|
||||
class="btn"
|
||||
:class="getClassButton"
|
||||
:title="buttonTitle"
|
||||
@click="openModal"
|
||||
@click="openModalChoose"
|
||||
>
|
||||
<span v-if="displayTextButton">{{ buttonTitle }}</span>
|
||||
</a>
|
||||
@@ -16,22 +16,28 @@
|
||||
:selected="selected"
|
||||
:modal-dialog-class="'modal-dialog-scrollable modal-xl'"
|
||||
:allow-create="props.allowCreate"
|
||||
@close="closeModal"
|
||||
@addNewPersons="payload => emit('addNewPersons', payload)"
|
||||
@close="closeModalChoose"
|
||||
@addNewPersons="(payload) => emit('addNewPersons', payload)"
|
||||
@onAskForCreate="onAskForCreate"
|
||||
/>
|
||||
|
||||
<CreateModal
|
||||
v-if="creatableEntityTypes.length > 0 && showModalCreate"
|
||||
:allowed-types="creatableEntityTypes"
|
||||
></CreateModal>
|
||||
@close="closeModalCreate"
|
||||
></CreateModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import PersonChooseModal from './AddPersons/PersonChooseModal.vue';
|
||||
import type {Suggestion, SearchOptions, CreatableEntityType, EntityType} from 'ChillPersonAssets/types';
|
||||
import {marked} from "marked";
|
||||
import { ref, computed } from "vue";
|
||||
import PersonChooseModal from "./AddPersons/PersonChooseModal.vue";
|
||||
import type {
|
||||
Suggestion,
|
||||
SearchOptions,
|
||||
CreatableEntityType,
|
||||
EntityType,
|
||||
} from "ChillPersonAssets/types";
|
||||
import { marked } from "marked";
|
||||
import options = marked.options;
|
||||
import CreateModal from "ChillMainAssets/vuejs/OnTheFly/components/CreateModal.vue";
|
||||
|
||||
@@ -42,7 +48,7 @@ interface AddPersonsConfig {
|
||||
modalTitle: string;
|
||||
options: SearchOptions;
|
||||
allowCreate?: boolean;
|
||||
types?: EntityType|undefined;
|
||||
types?: EntityType | undefined;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<AddPersonsConfig>(), {
|
||||
@@ -52,43 +58,54 @@ const props = withDefaults(defineProps<AddPersonsConfig>(), {
|
||||
types: () => undefined,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'addNewPersons', payload: { selected: Suggestion[] }): void;
|
||||
}>();
|
||||
const emit =
|
||||
defineEmits<
|
||||
(e: "addNewPersons", payload: { selected: Suggestion[] }) => void
|
||||
>();
|
||||
|
||||
const showModalChoose = ref(false);
|
||||
const showModalCreate = ref(false);
|
||||
|
||||
const getClassButton = computed(() => {
|
||||
const size = props.options?.button?.size ?? '';
|
||||
const type = props.options?.button?.type ?? 'btn-create';
|
||||
const size = props.options?.button?.size ?? "";
|
||||
const type = props.options?.button?.type ?? "btn-create";
|
||||
return size ? `${size} ${type}` : type;
|
||||
});
|
||||
|
||||
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[]>(() => {
|
||||
if (typeof props.options.type !== 'undefined') {
|
||||
return props.options.type.filter((e: EntityType) => e === 'thirdparty' || e === 'person');
|
||||
if (typeof props.options.type !== "undefined") {
|
||||
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}) {
|
||||
console.log('onAskForCreate', query);
|
||||
function onAskForCreate({ query }: { query: string }) {
|
||||
console.log("onAskForCreate", query);
|
||||
showModalChoose.value = false;
|
||||
showModalCreate.value = true;
|
||||
}
|
||||
|
||||
function openModal() {
|
||||
function openModalChoose() {
|
||||
showModalChoose.value = true;
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
function closeModalChoose() {
|
||||
showModalChoose.value = false;
|
||||
}
|
||||
|
||||
function closeModalCreate() {
|
||||
showModalCreate.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@@ -14,7 +14,9 @@
|
||||
<div class="search">
|
||||
<label class="col-form-label" style="float: right">
|
||||
{{
|
||||
trans(ADD_PERSONS_SUGGESTED_COUNTER, { count: suggestedCounter })
|
||||
trans(ADD_PERSONS_SUGGESTED_COUNTER, {
|
||||
count: suggestedCounter,
|
||||
})
|
||||
}}
|
||||
</label>
|
||||
|
||||
@@ -26,7 +28,11 @@
|
||||
ref="searchRef"
|
||||
/>
|
||||
<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>
|
||||
|
||||
@@ -42,7 +48,9 @@
|
||||
</a>
|
||||
</span>
|
||||
<span v-if="selectedCounter > 0">
|
||||
{{ trans(ADD_PERSONS_SELECTED_COUNTER, { count: selectedCounter }) }}
|
||||
{{
|
||||
trans(ADD_PERSONS_SELECTED_COUNTER, { count: selectedCounter })
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -60,11 +68,11 @@
|
||||
@update-selected="updateSelected"
|
||||
/>
|
||||
|
||||
<div v-if="props.allowCreate && query.length > 0" class="create-button">
|
||||
<button
|
||||
type="button"
|
||||
@click="emit('onAskForCreate', {query })"
|
||||
>
|
||||
<div
|
||||
v-if="props.allowCreate && query.length > 0"
|
||||
class="create-button"
|
||||
>
|
||||
<button type="button" @click="emit('onAskForCreate', { query })">
|
||||
{{ trans(ONTHEFLY_CREATE_BUTTON, { q: query }) }}
|
||||
</button>
|
||||
<!--
|
||||
@@ -100,12 +108,12 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, reactive, computed, nextTick, watch, onMounted} from 'vue';
|
||||
import Modal from 'ChillMainAssets/vuejs/_components/Modal.vue';
|
||||
import PersonSuggestion from './PersonSuggestion.vue';
|
||||
import OnTheFly from 'ChillMainAssets/vuejs/OnTheFly/components/OnTheFly.vue';
|
||||
import { searchEntities } from 'ChillPersonAssets/vuejs/_api/AddPersons';
|
||||
import { makeFetch } from 'ChillMainAssets/lib/api/apiMethods';
|
||||
import { ref, reactive, computed, nextTick, watch, onMounted } from "vue";
|
||||
import Modal from "ChillMainAssets/vuejs/_components/Modal.vue";
|
||||
import PersonSuggestion from "./PersonSuggestion.vue";
|
||||
import OnTheFly from "ChillMainAssets/vuejs/OnTheFly/components/OnTheFly.vue";
|
||||
import { searchEntities } from "ChillPersonAssets/vuejs/_api/AddPersons";
|
||||
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
|
||||
|
||||
import {
|
||||
trans,
|
||||
@@ -116,14 +124,15 @@ import {
|
||||
ACTION_CHECK_ALL,
|
||||
ACTION_RESET,
|
||||
ACTION_ADD,
|
||||
} from 'translator';
|
||||
} from "translator";
|
||||
|
||||
import type {
|
||||
Suggestion,
|
||||
Search,
|
||||
AddPersonResult as OriginalResult,
|
||||
SearchOptions, EntitiesOrMe,
|
||||
} from 'ChillPersonAssets/types';
|
||||
SearchOptions,
|
||||
EntitiesOrMe,
|
||||
} from "ChillPersonAssets/types";
|
||||
|
||||
type Result = OriginalResult & { addressId?: number };
|
||||
|
||||
@@ -139,16 +148,16 @@ interface Props {
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
suggested: () => [],
|
||||
selected: () => [],
|
||||
modalDialogClass: 'modal-dialog-scrollable modal-xl',
|
||||
modalDialogClass: "modal-dialog-scrollable modal-xl",
|
||||
allowCreate: () => true,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
(e: "close"): void;
|
||||
/** @deprecated use 'onPickEntities' */
|
||||
(e: 'addNewPersons', payload: { selected: Suggestion[] }): void;
|
||||
(e: 'onPickEntities', payload: { selected: EntitiesOrMe[] }): void;
|
||||
(e: 'onAskForCreate', payload: { query: string }): void;
|
||||
(e: "addNewPersons", payload: { selected: Suggestion[] }): void;
|
||||
(e: "onPickEntities", payload: { selected: EntitiesOrMe[] }): void;
|
||||
(e: "onAskForCreate", payload: { query: string }): void;
|
||||
}>();
|
||||
|
||||
const searchRef = ref<HTMLInputElement | null>(null);
|
||||
@@ -160,8 +169,8 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
const search = reactive({
|
||||
query: '' as string,
|
||||
previousQuery: '' as string,
|
||||
query: "" as string,
|
||||
previousQuery: "" as string,
|
||||
currentSearchQueryController: null as AbortController | null,
|
||||
suggested: (props.suggested ?? []) as Suggestion[],
|
||||
selected: (props.selected ?? []) as Suggestion[],
|
||||
@@ -173,7 +182,7 @@ watch(
|
||||
(newSelected) => {
|
||||
search.selected = newSelected ? [...newSelected] : [];
|
||||
},
|
||||
{ deep: true }
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
watch(
|
||||
@@ -181,7 +190,7 @@ watch(
|
||||
(newSuggested) => {
|
||||
search.suggested = newSuggested ? [...newSuggested] : [];
|
||||
},
|
||||
{ deep: true }
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
const query = computed({
|
||||
@@ -193,7 +202,9 @@ const suggestedCounter = computed(() => search.suggested.length);
|
||||
const selectedComputed = computed<Suggestion[]>(() => search.selected);
|
||||
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 hasPriorSuggestion = computed(() => !!search.priorSuggestion.key);
|
||||
@@ -230,7 +241,7 @@ function setQuery(q: string) {
|
||||
search.currentSearchQueryController = null;
|
||||
}
|
||||
|
||||
if (q === '') {
|
||||
if (q === "") {
|
||||
loadSuggestions([]);
|
||||
return;
|
||||
}
|
||||
@@ -250,7 +261,7 @@ function setQuery(q: string) {
|
||||
loadSuggestions(suggested.results);
|
||||
})
|
||||
.catch((error: DOMException) => {
|
||||
if (error instanceof DOMException && error.name === 'AbortError') {
|
||||
if (error instanceof DOMException && error.name === "AbortError") {
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
@@ -270,7 +281,7 @@ function updateSelected(value: Suggestion[]) {
|
||||
}
|
||||
|
||||
function resetSuggestion() {
|
||||
search.query = '';
|
||||
search.query = "";
|
||||
search.suggested = [];
|
||||
}
|
||||
|
||||
@@ -306,10 +317,12 @@ function newPriorSuggestion(entity: Result | null) {
|
||||
* Triggered when the user clicks on the "add" button.
|
||||
*/
|
||||
function pickEntities(): void {
|
||||
emit('addNewPersons', { selected: search.selected });
|
||||
emit('onPickEntities', {selected: search.selected.map((s: Suggestion) => s.result )})
|
||||
search.query = '';
|
||||
emit('close');
|
||||
emit("addNewPersons", { selected: search.selected });
|
||||
emit("onPickEntities", {
|
||||
selected: search.selected.map((s: Suggestion) => s.result),
|
||||
});
|
||||
search.query = "";
|
||||
emit("close");
|
||||
}
|
||||
|
||||
/*
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div v-if="action === 'show'">
|
||||
<div v-if="action === 'show' && person !== null">
|
||||
<div class="flex-table">
|
||||
<person-render-box
|
||||
render="bloc"
|
||||
@@ -22,445 +22,48 @@
|
||||
</div>
|
||||
|
||||
<div v-else-if="action === 'edit' || action === 'create'">
|
||||
<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>
|
||||
|
||||
<!-- 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>
|
||||
<PersonEdit
|
||||
:id="props.id"
|
||||
:type="props.type"
|
||||
:action="props.action"
|
||||
:query="props.query"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted } from "vue";
|
||||
import {
|
||||
getCentersForPersonCreation,
|
||||
getCivilities,
|
||||
getGenders,
|
||||
getPerson,
|
||||
getPersonAltNames,
|
||||
} from "../../_api/OnTheFly";
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from "vue";
|
||||
import { getPerson } from "../../_api/OnTheFly";
|
||||
import PersonRenderBox from "../Entity/PersonRenderBox.vue";
|
||||
import AddAddress from "ChillMainAssets/vuejs/Address/components/AddAddress.vue";
|
||||
import { localizeString } from "ChillMainAssets/lib/localizationHelper/localizationHelper";
|
||||
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 PersonEdit from "./PersonEdit.vue";
|
||||
import type { Person } from "ChillPersonAssets/types";
|
||||
|
||||
const props = defineProps({
|
||||
id: [String, Number],
|
||||
type: String,
|
||||
action: 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é");
|
||||
}
|
||||
interface Props {
|
||||
id: string | number;
|
||||
type?: string;
|
||||
action: "show" | "edit" | "create";
|
||||
query?: string;
|
||||
}
|
||||
|
||||
function loadData() {
|
||||
getPerson(props.id).then((p) => {
|
||||
Object.assign(person, p);
|
||||
const props = defineProps<Props>();
|
||||
|
||||
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(() => {
|
||||
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") {
|
||||
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>
|
||||
|
@@ -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>
|
Reference in New Issue
Block a user