Restore features after merging

This commit is contained in:
Julien Fastré 2025-07-09 17:46:16 +02:00
parent 392fd01b56
commit 0204bdd38d
Signed by: julienfastre
GPG Key ID: BDE2190974723FCB
3 changed files with 605 additions and 588 deletions

View File

@ -1,14 +1,18 @@
<template>
<div class="grey-card">
<ul :class="listClasses" v-if="picked.length && displayPicked">
<li v-for="p in picked" @click="removeEntity(p)" :key="p.type + p.id">
<span
v-if="'me' === p"
class="chill_denomination current-user updatedBy"
>{{ trans(USER_CURRENT_USER) }}</span
>
<li
v-for="p in picked"
@click="removeEntity(p)"
:key="p === 'me' ? 'me' : p.type + p.id"
>
<span
v-else
v-if="'me' === p"
class="chill_denomination current-user updatedBy"
>{{ trans(USER_CURRENT_USER) }}</span
>
<span
v-else
:class="getBadgeClass(p)"
class="chill_denomination"
:style="getBadgeStyle(p)"
@ -19,17 +23,17 @@
</ul>
<ul class="record_actions">
<li v-if="isCurrentUserPicker" class="btn btn-sm btn-misc">
<label class="flex items-center gap-2">
<input
:checked="picked.indexOf('me') >= 0 ? true : null"
ref="itsMeCheckbox"
:type="multiple ? 'checkbox' : 'radio'"
@change="selectItsMe"
/>
{{ trans(USER_CURRENT_USER) }}
</label>
</li>
<li class="add-persons">
<label class="flex items-center gap-2">
<input
:checked="isMePicked"
ref="itsMeCheckbox"
:type="multiple ? 'checkbox' : 'radio'"
@change="selectItsMe($event as InputEvent)"
/>
{{ trans(USER_CURRENT_USER) }}
</label>
</li>
<li class="add-persons">
<add-persons
:options="addPersonsOptions"
:key="uniqid"
@ -42,7 +46,11 @@
</ul>
<ul class="badge-suggest add-items inline" style="float: right">
<li v-for="s in suggested" :key="s.id" @click="addNewSuggested(s)">
<li
v-for="s in suggested"
:key="s.type + s.id"
@click="addNewSuggested(s)"
>
<span :class="getBadgeClass(s)" :style="getBadgeStyle(s)">
{{ s.text }}
</span>
@ -52,16 +60,28 @@
</template>
<script lang="ts" setup>
import { ref, computed, defineProps, defineEmits, defineComponent } from "vue";
import {
ref,
computed,
defineProps,
defineEmits,
defineComponent,
withDefaults,
} from "vue";
import AddPersons from "ChillPersonAssets/vuejs/_components/AddPersons.vue";
import { Entities, EntityType, SearchOptions } from "ChillPersonAssets/types";
import {
Entities,
EntitiesOrMe,
EntityType,
SearchOptions,
} from "ChillPersonAssets/types";
import {
PICK_ENTITY_MODAL_TITLE,
PICK_ENTITY_USER,
PICK_ENTITY_USER_GROUP,
PICK_ENTITY_PERSON,
PICK_ENTITY_THIRDPARTY,
USER_CURRENT_USER,
USER_CURRENT_USER,
trans,
} from "translator";
import { addNewEntities } from "ChillMainAssets/types";
@ -71,25 +91,28 @@ defineComponent({
AddPersons,
},
});
const props = defineProps<{
multiple: boolean;
types: EntityType[];
picked: Entities[];
uniqid: string;
removableIfSet?: boolean;
displayPicked?: boolean;
suggested?: Entities[];
label?: string;
isCurrentUserPicker: boolean // must default to false
}>();
const props = withDefaults(
defineProps<{
multiple: boolean;
types: EntityType[];
picked: EntitiesOrMe[];
uniqid: string;
removableIfSet?: boolean;
displayPicked?: boolean;
suggested?: Entities[];
label?: string;
isCurrentUserPicker?: boolean;
}>(),
{ isCurrentUserPicker: false },
);
const emits = defineEmits<{
(e: "addNewEntity", payload: { entity: Entities }): void;
(e: "removeEntity", payload: { entity: Entities }): void;
(e: "addNewEntity", payload: { entity: EntitiesOrMe }): void;
(e: "removeEntity", payload: { entity: EntitiesOrMe }): void;
(e: "addNewEntityProcessEnded"): void;
}>();
const itsMeCheckbox = ref(null);
const itsMeCheckbox = ref<null | HTMLInputElement>(null);
const addPersons = ref();
const addPersonsOptions = computed(
@ -105,6 +128,8 @@ const addPersonsOptions = computed(
}) as SearchOptions,
);
const isMePicked = computed<boolean>(() => props.picked.indexOf("me") >= 0);
const translatedListOfTypes = computed(() => {
if (props.label !== undefined && props.label !== "") {
return props.label;
@ -142,10 +167,12 @@ const listClasses = computed(() => ({
inline: true,
}));
const selectItsMe = (event) =>
event.target.checked ? addNewSuggested("me") : removeEntity("me");
const selectItsMe = (event: InputEvent) => {
const target = event.target as HTMLInputElement;
target.checked ? addNewSuggested("me") : removeEntity("me");
};
function addNewSuggested(entity: Entities) {
function addNewSuggested(entity: EntitiesOrMe) {
emits("addNewEntity", { entity });
}
@ -158,32 +185,31 @@ function addNewEntity({ selected }: addNewEntities) {
emits("addNewEntityProcessEnded");
}
const removeEntity = (entity) => {
if (!props.removableIfSet) return;
if (entity === "me" && itsMeCheckbox.value) {
itsMeCheckbox.value.checked = false;
}
emits("removeEntity", { entity });
const removeEntity = (entity: EntitiesOrMe) => {
if (!props.removableIfSet) return;
if (entity === "me" && itsMeCheckbox.value) {
itsMeCheckbox.value.checked = false;
}
emits("removeEntity", { entity });
};
function getBadgeClass(entities: Entities) {
if (entities.type !== "user_group") {
return entities.type;
}
return "";
if (entities.type !== "user_group") {
return entities.type;
}
return "";
}
function getBadgeStyle(entities: Entities) {
if (entities.type === "user_group") {
return [
`ul.badge-suggest li > span {
if (entities.type === "user_group") {
return [
`ul.badge-suggest li > span {
color: ${entities.foregroundColor}!important;
border-bottom-color: ${entities.backgroundColor};
}`,
];
}
return [];
];
}
return [];
}
</script>
@ -291,7 +317,7 @@ ul.badge-suggest li > span.thirdparty {
border-bottom-color: rgb(198.9, 72, 98.1);
}
.current-user {
color: var(--bs-body-color);
background-color: var(--bs-chill-l-gray) !important;
color: var(--bs-body-color);
background-color: var(--bs-chill-l-gray) !important;
}
</style>

View File

@ -1,4 +1,3 @@
import { StoredObject } from "ChillDocStoreAssets/types";
import {
Address,
Center,
@ -9,8 +8,8 @@ import {
Household,
WorkflowAvailable,
Scope,
Job,
PrivateCommentEmbeddable,
Job,
PrivateCommentEmbeddable,
} from "ChillMainAssets/types";
import { StoredObject } from "ChillDocStoreAssets/types";
import { Thirdparty } from "../../../ChillThirdPartyBundle/Resources/public/types";
@ -45,208 +44,208 @@ export interface Person {
}
export interface AccompanyingPeriod {
id: number;
addressLocation?: Address | null;
administrativeLocation?: Location | null;
calendars: Calendar[];
closingDate?: Date | null;
closingMotive?: ClosingMotive | null;
comments: Comment[];
confidential: boolean;
createdAt?: Date | null;
createdBy?: User | null;
emergency: boolean;
intensity?: "occasional" | "regular";
job?: Job | null;
locationHistories: AccompanyingPeriodLocationHistory[];
openingDate?: Date | null;
origin?: Origin | null;
participations: AccompanyingPeriodParticipation[];
personLocation?: Person | null;
pinnedComment?: Comment | null;
preventUserIsChangedNotification: boolean;
remark: string;
requestorAnonymous: boolean;
requestorPerson?: Person | null;
requestorThirdParty?: Thirdparty | null;
resources: AccompanyingPeriodResource[];
scopes: Scope[];
socialIssues: SocialIssue[];
step?:
| "CLOSED"
| "CONFIRMED"
| "CONFIRMED_INACTIVE_SHORT"
| "CONFIRMED_INACTIVE_LONG"
| "DRAFT";
id: number;
addressLocation?: Address | null;
administrativeLocation?: Location | null;
calendars: Calendar[];
closingDate?: Date | null;
closingMotive?: ClosingMotive | null;
comments: Comment[];
confidential: boolean;
createdAt?: Date | null;
createdBy?: User | null;
emergency: boolean;
intensity?: "occasional" | "regular";
job?: Job | null;
locationHistories: AccompanyingPeriodLocationHistory[];
openingDate?: Date | null;
origin?: Origin | null;
participations: AccompanyingPeriodParticipation[];
personLocation?: Person | null;
pinnedComment?: Comment | null;
preventUserIsChangedNotification: boolean;
remark: string;
requestorAnonymous: boolean;
requestorPerson?: Person | null;
requestorThirdParty?: Thirdparty | null;
resources: AccompanyingPeriodResource[];
scopes: Scope[];
socialIssues: SocialIssue[];
step?:
| "CLOSED"
| "CONFIRMED"
| "CONFIRMED_INACTIVE_SHORT"
| "CONFIRMED_INACTIVE_LONG"
| "DRAFT";
}
export interface AccompanyingPeriodWork {
id: number;
accompanyingPeriod?: AccompanyingPeriod;
accompanyingPeriodWorkEvaluations: AccompanyingPeriodWorkEvaluation[];
createdAt?: string;
createdAutomatically: boolean;
createdAutomaticallyReason: string;
createdBy: User;
endDate?: string;
goals: AccompanyingPeriodWorkGoal[];
handlingThierParty?: Thirdparty;
note: string;
persons: Person[];
privateComment: PrivateCommentEmbeddable;
referrersHistory: AccompanyingPeriodWorkReferrerHistory[];
results: Result[];
socialAction?: SocialAction;
startDate?: string;
thirdParties: Thirdparty[];
updatedAt?: string;
updatedBy: User;
version: number;
id: number;
accompanyingPeriod?: AccompanyingPeriod;
accompanyingPeriodWorkEvaluations: AccompanyingPeriodWorkEvaluation[];
createdAt?: string;
createdAutomatically: boolean;
createdAutomaticallyReason: string;
createdBy: User;
endDate?: string;
goals: AccompanyingPeriodWorkGoal[];
handlingThierParty?: Thirdparty;
note: string;
persons: Person[];
privateComment: PrivateCommentEmbeddable;
referrersHistory: AccompanyingPeriodWorkReferrerHistory[];
results: Result[];
socialAction?: SocialAction;
startDate?: string;
thirdParties: Thirdparty[];
updatedAt?: string;
updatedBy: User;
version: number;
}
export interface SocialAction {
id: number;
parent?: SocialAction | null;
children: SocialAction[];
issue?: SocialIssue | null;
ordering: number;
title: {
fr: string;
};
defaultNotificationDelay?: string | null;
desactivationDate?: string | null;
evaluations: Evaluation[];
goals: Goal[];
results: Result[];
id: number;
parent?: SocialAction | null;
children: SocialAction[];
issue?: SocialIssue | null;
ordering: number;
title: {
fr: string;
};
defaultNotificationDelay?: string | null;
desactivationDate?: string | null;
evaluations: Evaluation[];
goals: Goal[];
results: Result[];
}
export interface AccompanyingPeriodResource {
id: number;
accompanyingPeriod: AccompanyingPeriod;
comment?: string | null;
person?: Person | null;
thirdParty?: Thirdparty | null;
id: number;
accompanyingPeriod: AccompanyingPeriod;
comment?: string | null;
person?: Person | null;
thirdParty?: Thirdparty | null;
}
export interface Origin {
id: number;
label: {
fr: string;
};
noActiveAfter: DateTime;
id: number;
label: {
fr: string;
};
noActiveAfter: DateTime;
}
export interface ClosingMotive {
id: number;
active: boolean;
name: {
fr: string;
};
ordering: number;
isCanceledAccompanyingPeriod: boolean;
parent?: ClosingMotive | null;
children: ClosingMotive[];
id: number;
active: boolean;
name: {
fr: string;
};
ordering: number;
isCanceledAccompanyingPeriod: boolean;
parent?: ClosingMotive | null;
children: ClosingMotive[];
}
export interface AccompanyingPeriodParticipation {
id: number;
startDate: DateTime;
endDate?: DateTime | null;
accompanyingPeriod: AccompanyingPeriod;
person: Person;
id: number;
startDate: DateTime;
endDate?: DateTime | null;
accompanyingPeriod: AccompanyingPeriod;
person: Person;
}
export interface AccompanyingPeriodLocationHistory {
id: number;
startDate: DateTime;
endDate?: DateTime | null;
addressLocation?: Address | null;
period: AccompanyingPeriod;
personLocation?: Person | null;
id: number;
startDate: DateTime;
endDate?: DateTime | null;
addressLocation?: Address | null;
period: AccompanyingPeriod;
personLocation?: Person | null;
}
export interface SocialIssue {
id: number;
parent?: SocialIssue | null;
children: SocialIssue[];
socialActions?: SocialAction[] | null;
ordering: number;
title: {
fr: string;
};
desactivationDate?: string | null;
id: number;
parent?: SocialIssue | null;
children: SocialIssue[];
socialActions?: SocialAction[] | null;
ordering: number;
title: {
fr: string;
};
desactivationDate?: string | null;
}
export interface Goal {
id: number;
results: Result[];
socialActions?: SocialAction[] | null;
title: {
fr: string;
};
id: number;
results: Result[];
socialActions?: SocialAction[] | null;
title: {
fr: string;
};
}
export interface Result {
id: number;
accompanyingPeriodWorks: AccompanyingPeriodWork[];
accompanyingPeriodWorkGoals: AccompanyingPeriodWorkGoal[];
goals: Goal[];
socialActions: SocialAction[];
title: {
fr: string;
};
desactivationDate?: string | null;
id: number;
accompanyingPeriodWorks: AccompanyingPeriodWork[];
accompanyingPeriodWorkGoals: AccompanyingPeriodWorkGoal[];
goals: Goal[];
socialActions: SocialAction[];
title: {
fr: string;
};
desactivationDate?: string | null;
}
export interface AccompanyingPeriodWorkGoal {
id: number;
accompanyingPeriodWork: AccompanyingPeriodWork;
goal: Goal;
note: string;
results: Result[];
id: number;
accompanyingPeriodWork: AccompanyingPeriodWork;
goal: Goal;
note: string;
results: Result[];
}
export interface AccompanyingPeriodWorkEvaluation {
accompanyingPeriodWork: AccompanyingPeriodWork | null;
comment: string;
createdAt: DateTime | null;
createdBy: User | null;
documents: AccompanyingPeriodWorkEvaluationDocument[];
endDate: DateTime | null;
evaluation: Evaluation | null;
id: number | null;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
key: any;
maxDate: DateTime | null;
startDate: DateTime | null;
updatedAt: DateTime | null;
updatedBy: User | null;
warningInterval: string | null;
timeSpent: number | null;
accompanyingPeriodWork: AccompanyingPeriodWork | null;
comment: string;
createdAt: DateTime | null;
createdBy: User | null;
documents: AccompanyingPeriodWorkEvaluationDocument[];
endDate: DateTime | null;
evaluation: Evaluation | null;
id: number | null;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
key: any;
maxDate: DateTime | null;
startDate: DateTime | null;
updatedAt: DateTime | null;
updatedBy: User | null;
warningInterval: string | null;
timeSpent: number | null;
}
export interface Evaluation {
id: number;
url: string;
socialActions: SocialAction[];
title: {
fr: string;
};
active: boolean;
delay: string;
notificationDelay: string;
id: number;
url: string;
socialActions: SocialAction[];
title: {
fr: string;
};
active: boolean;
delay: string;
notificationDelay: string;
}
export interface AccompanyingPeriodWorkReferrerHistory {
id: number;
accompanyingPeriodWork: AccompanyingPeriodWork;
user: User;
startDate: DateTime;
endDate: DateTime | null;
createdAt: DateTime;
updatedAt: DateTime | null;
createdBy: User;
updatedBy: User | null;
id: number;
accompanyingPeriodWork: AccompanyingPeriodWork;
user: User;
startDate: DateTime;
endDate: DateTime | null;
createdAt: DateTime;
updatedAt: DateTime | null;
createdBy: User;
updatedBy: User | null;
}
export interface AccompanyingPeriodWorkEvaluationDocument {
@ -276,14 +275,16 @@ export type Entities = (UserGroup | User | Person | Thirdparty | Household) & {
profession?: string;
};
export type Result = Entities & {
export type EntitiesOrMe = "me" | Entities;
export type AddPersonResult = Entities & {
parent?: Entities | null;
};
export interface Suggestion {
key: string;
relevance: number;
result: Result;
result: AddPersonResult;
}
export interface SearchPagination {

View File

@ -1,131 +1,124 @@
<template>
<a
class="btn"
:class="getClassButton"
:title="buttonTitle"
@click="openModal"
<a
class="btn"
:class="getClassButton"
:title="buttonTitle"
@click="openModal"
>
<span v-if="displayTextButton">{{ buttonTitle }}</span>
</a>
<teleport to="body">
<modal
v-if="showModal"
@close="closeModal"
:modal-dialog-class="modalDialogClass"
:show="showModal"
:hide-footer="false"
>
<span v-if="displayTextButton">{{ buttonTitle }}</span>
</a>
<template #header>
<h3 class="modal-title">
{{ modalTitle }}
</h3>
</template>
<teleport to="body">
<modal
v-if="showModal"
@close="closeModal"
:modal-dialog-class="modalDialogClass"
:show="showModal"
:hide-footer="false"
<template #body-head>
<div class="modal-body">
<div class="search">
<label class="col-form-label" style="float: right">
{{
trans(ADD_PERSONS_SUGGESTED_COUNTER, {
count: suggestedCounter,
})
}}
</label>
<input
id="search-persons"
name="query"
v-model="query"
:placeholder="trans(ADD_PERSONS_SEARCH_SOME_PERSONS)"
ref="searchRef"
/>
<i class="fa fa-search fa-lg" />
<i
class="fa fa-times"
v-if="queryLength >= 3"
@click="resetSuggestion"
/>
</div>
</div>
<div class="modal-body" v-if="checkUniq === 'checkbox'">
<div class="count">
<span>
<a v-if="suggestedCounter > 2" @click="selectAll">
{{ trans(ACTION_CHECK_ALL) }}
</a>
<a v-if="selectedCounter > 0" @click="resetSelection">
<i v-if="suggestedCounter > 2"> </i>
{{ trans(ACTION_RESET) }}
</a>
</span>
<span v-if="selectedCounter > 0">
{{
trans(ADD_PERSONS_SELECTED_COUNTER, {
count: selectedCounter,
})
}}
</span>
</div>
</div>
</template>
<template #body>
<div class="results">
<person-suggestion
v-for="item in selectedAndSuggested.slice().reverse()"
:key="itemKey(item)"
:item="item"
:search="search"
:type="checkUniq"
@save-form-on-the-fly="saveFormOnTheFly"
@new-prior-suggestion="newPriorSuggestion"
@update-selected="updateSelected"
/>
<div class="create-button">
<on-the-fly
v-if="
queryLength >= 3 &&
(options.type.includes('person') ||
options.type.includes('thirdparty'))
"
:button-text="trans(ONTHEFLY_CREATE_BUTTON, { q: query })"
:allowed-types="options.type"
:query="query"
action="create"
@save-form-on-the-fly="saveFormOnTheFly"
ref="onTheFly"
/>
</div>
</div>
</template>
<template #footer>
<button
class="btn btn-create"
@click.prevent="
() => {
$emit('addNewPersons', {
selected: selectedComputed,
});
query = '';
closeModal();
}
"
>
<template #header>
<h3 class="modal-title">
{{ modalTitle }}
</h3>
</template>
<template #body-head>
<div class="modal-body">
<div class="search">
<label class="col-form-label" style="float: right">
{{
trans(ADD_PERSONS_SUGGESTED_COUNTER, {
count: suggestedCounter,
})
}}
</label>
<input
id="search-persons"
name="query"
v-model="query"
:placeholder="
trans(ADD_PERSONS_SEARCH_SOME_PERSONS)
"
ref="searchRef"
/>
<i class="fa fa-search fa-lg" />
<i
class="fa fa-times"
v-if="queryLength >= 3"
@click="resetSuggestion"
/>
</div>
</div>
<div class="modal-body" v-if="checkUniq === 'checkbox'">
<div class="count">
<span>
<a v-if="suggestedCounter > 2" @click="selectAll">
{{ trans(ACTION_CHECK_ALL) }}
</a>
<a
v-if="selectedCounter > 0"
@click="resetSelection"
>
<i v-if="suggestedCounter > 2"> </i>
{{ trans(ACTION_RESET) }}
</a>
</span>
<span v-if="selectedCounter > 0">
{{
trans(ADD_PERSONS_SELECTED_COUNTER, {
count: selectedCounter,
})
}}
</span>
</div>
</div>
</template>
<template #body>
<div class="results">
<person-suggestion
v-for="item in selectedAndSuggested.slice().reverse()"
:key="itemKey(item)"
:item="item"
:search="search"
:type="checkUniq"
@save-form-on-the-fly="saveFormOnTheFly"
@new-prior-suggestion="newPriorSuggestion"
@update-selected="updateSelected"
/>
<div class="create-button">
<on-the-fly
v-if="
queryLength >= 3 &&
(options.type.includes('person') ||
options.type.includes('thirdparty'))
"
:button-text="
trans(ONTHEFLY_CREATE_BUTTON, { q: query })
"
:allowed-types="options.type"
:query="query"
action="create"
@save-form-on-the-fly="saveFormOnTheFly"
ref="onTheFly"
/>
</div>
</div>
</template>
<template #footer>
<button
class="btn btn-create"
@click.prevent="
() => {
$emit('addNewPersons', {
selected: selectedComputed,
});
query = '';
closeModal();
}
"
>
{{ trans(ACTION_ADD) }}
</button>
</template>
</modal>
</teleport>
{{ trans(ACTION_ADD) }}
</button>
</template>
</modal>
</teleport>
</template>
<script setup lang="ts">
@ -139,31 +132,31 @@ import OnTheFly from "ChillMainAssets/vuejs/OnTheFly/components/OnTheFly.vue";
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
import {
trans,
ADD_PERSONS_SUGGESTED_COUNTER,
ADD_PERSONS_SEARCH_SOME_PERSONS,
ADD_PERSONS_SELECTED_COUNTER,
ONTHEFLY_CREATE_BUTTON,
ACTION_CHECK_ALL,
ACTION_RESET,
ACTION_ADD,
trans,
ADD_PERSONS_SUGGESTED_COUNTER,
ADD_PERSONS_SEARCH_SOME_PERSONS,
ADD_PERSONS_SELECTED_COUNTER,
ONTHEFLY_CREATE_BUTTON,
ACTION_CHECK_ALL,
ACTION_RESET,
ACTION_ADD,
} from "translator";
import {
Suggestion,
Search,
Result as OriginalResult,
SearchOptions,
Suggestion,
Search,
AddPersonResult as OriginalResult,
SearchOptions,
} from "ChillPersonAssets/types";
// Extend Result type to include optional addressId
type Result = OriginalResult & { addressId?: number };
const props = defineProps({
suggested: { type: Array as () => Suggestion[], default: () => [] },
selected: { type: Array as () => Suggestion[], default: () => [] },
buttonTitle: { type: String, required: true },
modalTitle: { type: String, required: true },
options: { type: Object as () => SearchOptions, required: true },
suggested: { type: Array as () => Suggestion[], default: () => [] },
selected: { type: Array as () => Suggestion[], default: () => [] },
buttonTitle: { type: String, required: true },
modalTitle: { type: String, required: true },
options: { type: Object as () => SearchOptions, required: true },
});
defineEmits(["addNewPersons"]);
@ -172,25 +165,25 @@ const showModal = ref(false);
const modalDialogClass = ref("modal-dialog-scrollable modal-xl");
const modal = shallowRef({
showModal: false,
modalDialogClass: "modal-dialog-scrollable modal-xl",
showModal: false,
modalDialogClass: "modal-dialog-scrollable modal-xl",
});
const search = reactive({
query: "" as string,
previousQuery: "" as string,
currentSearchQueryController: null as AbortController | null,
suggested: props.suggested as Suggestion[],
selected: props.selected as Suggestion[],
priorSuggestion: {} as Partial<Suggestion>,
query: "" as string,
previousQuery: "" as string,
currentSearchQueryController: null as AbortController | null,
suggested: props.suggested as Suggestion[],
selected: props.selected as Suggestion[],
priorSuggestion: {} as Partial<Suggestion>,
});
const searchRef = ref<HTMLInputElement | null>(null);
const onTheFly = ref<InstanceType<typeof OnTheFly> | null>(null);
const query = computed({
get: () => search.query,
set: (val) => setQuery(val),
get: () => search.query,
set: (val) => setQuery(val),
});
const queryLength = computed(() => search.query.length);
const suggestedCounter = computed(() => search.suggested.length);
@ -198,18 +191,18 @@ const selectedComputed = computed(() => search.selected);
const selectedCounter = computed(() => search.selected.length);
const getClassButton = computed(() => {
let size = props.options?.button?.size ?? "";
let type = props.options?.button?.type ?? "btn-create";
return size ? size + " " + type : type;
let size = props.options?.button?.size ?? "";
let 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 checkUniq = computed(() =>
props.options.uniq === true ? "radio" : "checkbox",
props.options.uniq === true ? "radio" : "checkbox",
);
const priorSuggestion = computed(() => search.priorSuggestion);
@ -218,282 +211,279 @@ const hasPriorSuggestion = computed(() => !!search.priorSuggestion.key);
const itemKey = (item: Suggestion) => item.result.type + item.result.id;
function addPriorSuggestion() {
if (hasPriorSuggestion.value) {
// Type assertion is safe here due to the checks above
search.suggested.unshift(priorSuggestion.value as Suggestion);
search.selected.unshift(priorSuggestion.value as Suggestion);
newPriorSuggestion(null);
}
if (hasPriorSuggestion.value) {
// Type assertion is safe here due to the checks above
search.suggested.unshift(priorSuggestion.value as Suggestion);
search.selected.unshift(priorSuggestion.value as Suggestion);
newPriorSuggestion(null);
}
}
const selectedAndSuggested = computed(() => {
addPriorSuggestion();
const uniqBy = (a: Suggestion[], key: (item: Suggestion) => string) => [
...new Map(a.map((x) => [key(x), x])).values(),
];
let union = [
...new Set([
...search.suggested.slice().reverse(),
...search.selected.slice().reverse(),
]),
];
return uniqBy(union, (k: Suggestion) => k.key);
addPriorSuggestion();
const uniqBy = (a: Suggestion[], key: (item: Suggestion) => string) => [
...new Map(a.map((x) => [key(x), x])).values(),
];
let union = [
...new Set([
...search.suggested.slice().reverse(),
...search.selected.slice().reverse(),
]),
];
return uniqBy(union, (k: Suggestion) => k.key);
});
function openModal() {
showModal.value = true;
nextTick(() => {
if (searchRef.value) searchRef.value.focus();
});
showModal.value = true;
nextTick(() => {
if (searchRef.value) searchRef.value.focus();
});
}
function closeModal() {
showModal.value = false;
showModal.value = false;
}
function setQuery(q: string) {
search.query = q;
search.query = q;
// Clear previous search if any
if (search.currentSearchQueryController) {
search.currentSearchQueryController.abort();
search.currentSearchQueryController = null;
}
// Clear previous search if any
if (search.currentSearchQueryController) {
search.currentSearchQueryController.abort();
search.currentSearchQueryController = null;
}
if (q === "") {
loadSuggestions([]);
return;
}
if (q === "") {
loadSuggestions([]);
return;
}
// Debounce delay based on query length
const delay = q.length > 3 ? 300 : 700;
// Debounce delay based on query length
const delay = q.length > 3 ? 300 : 700;
setTimeout(() => {
// Only search if query hasn't changed in the meantime
if (q !== search.query) return;
setTimeout(() => {
// Only search if query hasn't changed in the meantime
if (q !== search.query) return;
search.currentSearchQueryController = new AbortController();
search.currentSearchQueryController = new AbortController();
searchEntities(
{ query: q, options: props.options },
search.currentSearchQueryController.signal,
)
.then((suggested: Search) => {
loadSuggestions(suggested.results);
})
.catch((error: DOMException) => {
if (
error instanceof DOMException &&
error.name === "AbortError"
) {
// Request was aborted, ignore
return;
}
throw error;
});
}, delay);
searchEntities(
{ query: q, options: props.options },
search.currentSearchQueryController.signal,
)
.then((suggested: Search) => {
loadSuggestions(suggested.results);
})
.catch((error: DOMException) => {
if (error instanceof DOMException && error.name === "AbortError") {
// Request was aborted, ignore
return;
}
throw error;
});
}, delay);
}
function loadSuggestions(suggestedArr: Suggestion[]) {
search.suggested = suggestedArr;
search.suggested.forEach((item) => {
item.key = itemKey(item);
});
search.suggested = suggestedArr;
search.suggested.forEach((item) => {
item.key = itemKey(item);
});
}
function updateSelected(value: Suggestion[]) {
search.selected = value;
search.selected = value;
}
function resetSuggestion() {
search.query = "";
search.suggested = [];
search.query = "";
search.suggested = [];
}
function resetSelection() {
search.selected = [];
search.selected = [];
}
function resetSearch() {
resetSelection();
resetSuggestion();
resetSelection();
resetSuggestion();
}
function selectAll() {
search.suggested.forEach((item) => {
search.selected.push(item);
});
search.suggested.forEach((item) => {
search.selected.push(item);
});
}
function newPriorSuggestion(entity: Result | null) {
if (entity !== null) {
let suggestion = {
key: entity.type + entity.id,
relevance: 0.5,
result: entity,
};
search.priorSuggestion = suggestion;
} else {
search.priorSuggestion = {};
}
if (entity !== null) {
let suggestion = {
key: entity.type + entity.id,
relevance: 0.5,
result: entity,
};
search.priorSuggestion = suggestion;
} else {
search.priorSuggestion = {};
}
}
async function saveFormOnTheFly({
type,
data,
type,
data,
}: {
type: string;
data: Result;
type: string;
data: Result;
}) {
try {
if (type === "person") {
const responsePerson: Result = await makeFetch(
"POST",
"/api/1.0/person/person.json",
data,
);
newPriorSuggestion(responsePerson);
if (onTheFly.value) onTheFly.value.closeModal();
try {
if (type === "person") {
const responsePerson: Result = await makeFetch(
"POST",
"/api/1.0/person/person.json",
data,
);
newPriorSuggestion(responsePerson);
if (onTheFly.value) onTheFly.value.closeModal();
if (data.addressId != null) {
const household = { type: "household" };
const address = { id: data.addressId };
try {
const responseHousehold: Result = await makeFetch(
"POST",
"/api/1.0/person/household.json",
household,
);
const member = {
concerned: [
{
person: {
type: "person",
id: responsePerson.id,
},
start_date: {
datetime: `${new Date().toISOString().split("T")[0]}T00:00:00+02:00`,
},
holder: false,
comment: null,
},
],
destination: {
type: "household",
id: responseHousehold.id,
},
composition: null,
};
await makeFetch(
"POST",
"/api/1.0/person/household/members/move.json",
member,
);
try {
const _response = await makeFetch(
"POST",
`/api/1.0/person/household/${responseHousehold.id}/address.json`,
address,
);
console.log(_response);
} catch (error) {
console.error(error);
}
} catch (error) {
console.error(error);
}
}
} else if (type === "thirdparty") {
const response: Result = await makeFetch(
"POST",
"/api/1.0/thirdparty/thirdparty.json",
data,
if (data.addressId != null) {
const household = { type: "household" };
const address = { id: data.addressId };
try {
const responseHousehold: Result = await makeFetch(
"POST",
"/api/1.0/person/household.json",
household,
);
const member = {
concerned: [
{
person: {
type: "person",
id: responsePerson.id,
},
start_date: {
datetime: `${new Date().toISOString().split("T")[0]}T00:00:00+02:00`,
},
holder: false,
comment: null,
},
],
destination: {
type: "household",
id: responseHousehold.id,
},
composition: null,
};
await makeFetch(
"POST",
"/api/1.0/person/household/members/move.json",
member,
);
try {
const _response = await makeFetch(
"POST",
`/api/1.0/person/household/${responseHousehold.id}/address.json`,
address,
);
newPriorSuggestion(response);
if (onTheFly.value) onTheFly.value.closeModal();
console.log(_response);
} catch (error) {
console.error(error);
}
} catch (error) {
console.error(error);
}
} catch (error) {
console.error(error);
}
} else if (type === "thirdparty") {
const response: Result = await makeFetch(
"POST",
"/api/1.0/thirdparty/thirdparty.json",
data,
);
newPriorSuggestion(response);
if (onTheFly.value) onTheFly.value.closeModal();
}
} catch (error) {
console.error(error);
}
}
watch(
() => props.selected,
(newSelected) => {
search.selected = newSelected;
},
{ deep: true },
() => props.selected,
(newSelected) => {
search.selected = newSelected;
},
{ deep: true },
);
watch(
() => props.suggested,
(newSuggested) => {
search.suggested = newSuggested;
},
{ deep: true },
() => props.suggested,
(newSuggested) => {
search.suggested = newSuggested;
},
{ deep: true },
);
watch(
() => modal,
(val) => {
showModal.value = val.value.showModal;
modalDialogClass.value = val.value.modalDialogClass;
},
{ deep: true },
() => modal,
(val) => {
showModal.value = val.value.showModal;
modalDialogClass.value = val.value.modalDialogClass;
},
{ deep: true },
);
defineExpose({
resetSearch,
showModal,
resetSearch,
showModal,
});
</script>
<style lang="scss">
li.add-persons {
a {
cursor: pointer;
}
a {
cursor: pointer;
}
}
div.body-head {
overflow-y: unset;
div.modal-body:first-child {
margin: auto 4em;
div.search {
position: relative;
input {
width: 100%;
padding: 1.2em 1.5em 1.2em 2.5em;
//margin: 1em 0;
}
i {
position: absolute;
opacity: 0.5;
padding: 0.65em 0;
top: 50%;
}
i.fa-search {
left: 0.5em;
}
i.fa-times {
right: 1em;
padding: 0.75em 0;
cursor: pointer;
}
}
overflow-y: unset;
div.modal-body:first-child {
margin: auto 4em;
div.search {
position: relative;
input {
width: 100%;
padding: 1.2em 1.5em 1.2em 2.5em;
//margin: 1em 0;
}
i {
position: absolute;
opacity: 0.5;
padding: 0.65em 0;
top: 50%;
}
i.fa-search {
left: 0.5em;
}
i.fa-times {
right: 1em;
padding: 0.75em 0;
cursor: pointer;
}
}
div.modal-body:last-child {
padding-bottom: 0;
}
div.count {
margin: -0.5em 0 0.7em;
display: flex;
justify-content: space-between;
a {
cursor: pointer;
}
}
div.modal-body:last-child {
padding-bottom: 0;
}
div.count {
margin: -0.5em 0 0.7em;
display: flex;
justify-content: space-between;
a {
cursor: pointer;
}
}
}
.create-button > a {
margin-top: 0.5em;
margin-left: 2.6em;
margin-top: 0.5em;
margin-left: 2.6em;
}
</style>