Enhance entity creation: Add CreateModal and integrate with AddPersons workflow.

This commit is contained in:
2025-09-12 15:05:48 +02:00
parent a6a342d86b
commit 062167d8a0
6 changed files with 127 additions and 52 deletions

View File

@@ -1,5 +1,6 @@
import { GenericDoc } from "ChillDocStoreAssets/types/generic_doc";
import { StoredObject, StoredObjectStatus } from "ChillDocStoreAssets/types";
import {GenericDoc} from "ChillDocStoreAssets/types/generic_doc";
import {StoredObject, StoredObjectStatus} from "ChillDocStoreAssets/types";
import {CreatableEntityType} from "ChillPersonAssets/types";
export interface DateTime {
datetime: string;
@@ -278,3 +279,12 @@ export interface addNewEntities {
selected: Selected[];
modal: Modal;
}
/**
* Configuration for the CreateModal and Create component
*/
export interface CreateComponentConfig {
action?: string;
allowedTypes: CreatableEntityType[];
query?: string;
}

View File

@@ -46,34 +46,29 @@
/>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from "vue";
<script setup lang="ts">
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 {
trans,
ONTHEFLY_CREATE_PERSON,
ONTHEFLY_CREATE_THIRDPARTY,
} from "translator";
import {ONTHEFLY_CREATE_PERSON, ONTHEFLY_CREATE_THIRDPARTY, trans,} from "translator";
import {CreatableEntityType} from "ChillPersonAssets/types";
import {CreateComponentConfig} from "ChillMainAssets/types";
const props = defineProps({
action: String,
allowedTypes: Array,
query: String,
});
const props = defineProps<CreateComponentConfig>();
const type = ref<CreatableEntityType| null>(null);
const type = ref(null);
const radioType = computed({
const radioType = computed<CreatableEntityType| null>({
get: () => type.value,
set: (val) => {
set: (val: CreatableEntityType | null) => {
type.value = val;
console.log("## type:", val, ", action:", props.action);
},
});
const castPerson = ref(null);
const castThirdparty = ref(null);
type AnyComponentInstance = InstanceType<typeof OnTheFlyPerson> | InstanceType<typeof OnTheFlyThirdparty> | null;
const castPerson = ref<AnyComponentInstance>(null);
const castThirdparty = ref<AnyComponentInstance>(null);
onMounted(() => {
type.value =
@@ -82,22 +77,27 @@ onMounted(() => {
: "person";
});
function isActive(tab) {
function isActive(tab: CreatableEntityType) {
return type.value === tab;
}
function castDataByType() {
// 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 {
switch (radioType.value) {
case "person":
return castPerson.value.$data.person;
case "thirdparty":
let data = castThirdparty.value.$data.thirdparty;
if (data.address !== undefined && data.address !== null) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (castPerson.value as any)?.$data?.person;
case "thirdparty": {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let data: any = (castThirdparty.value as any)?.$data?.thirdparty;
if (data && data.address !== undefined && data.address !== null) {
data.address = { id: data.address.address_id };
} else {
} else if (data) {
data.address = null;
}
return data;
}
default:
throw Error("Invalid type of entity");
}

View File

@@ -0,0 +1,32 @@
<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";
const props = defineProps<CreateComponentConfig>();
</script>
<template>
<teleport to="body">
<modal
@close="() => emit('close')"
:modal-dialog-class="modalDialogClass"
:hide-footer="false"
>
<template #header>
<h3 class="modal-title">{{ modalTitle }}</h3>
</template>
<template #body-head>
<div class="modal-body">
<Create :allowed-types="props.allowed-types" :action="props.action" :query="props.query"></Create>
</div>
</template>
</modal>
</teleport>
</template>
<style scoped lang="scss">
</style>

View File

@@ -328,11 +328,20 @@ export interface AccompanyingPeriodWorkEvaluationDocument {
workflows: object[];
}
export type EntityType =
/**
* Entity types that a user can create
*/
export type CreatableEntityType =
| "person"
| "thirdparty";
/**
* Entities that can be search and selected by a user
*/
export type EntityType = CreatableEntityType
| "user_group"
| "user"
| "person"
| "thirdparty"
| "household";
export type Entities = (UserGroup | User | Person | Thirdparty | Household) & {
@@ -370,7 +379,8 @@ export interface Search {
export interface SearchOptions {
uniq: boolean;
type: string[];
/** @deprecated */
type: EntityType[];
priority: number | null;
button: {
size: string;

View File

@@ -9,22 +9,31 @@
</a>
<person-choose-modal
v-if="showModal"
:show="showModal"
v-if="showModalChoose"
:modal-title="modalTitle"
:options="options"
:suggested="suggested"
:selected="selected"
:modal-dialog-class="'modal-dialog-scrollable modal-xl'"
:allow-create="props.allowCreate"
@close="closeModal"
@addNewPersons="payload => $emit('addNewPersons', payload)"
@addNewPersons="payload => emit('addNewPersons', payload)"
@onAskForCreate="onAskForCreate"
/>
<CreateModal
v-if="creatableEntityTypes.length > 0 && showModalCreate"
:allowed-types="creatableEntityTypes"
></CreateModal>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import PersonChooseModal from './AddPersons/PersonChooseModal.vue';
import type { Suggestion, SearchOptions } from 'ChillPersonAssets/types';
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";
interface AddPersonsConfig {
suggested?: Suggestion[];
@@ -33,19 +42,22 @@ interface AddPersonsConfig {
modalTitle: string;
options: SearchOptions;
allowCreate?: boolean;
types?: EntityType|undefined;
}
const props = withDefaults(defineProps<AddPersonsConfig>(), {
suggested: () => [],
selected: () => [],
allowCreate: () => true,
types: () => undefined,
});
const emit = defineEmits<{
(e: 'addNewPersons', payload: { selected: Suggestion[] }): void;
}>();
const showModal = ref(false);
const showModalChoose = ref(false);
const showModalCreate = ref(false);
const getClassButton = computed(() => {
const size = props.options?.button?.size ?? '';
@@ -57,12 +69,25 @@ const displayTextButton = computed(() =>
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');
}
return props.type.filter((e: EntityType) => e === 'thirdparty' || e === 'person');
})
function onAskForCreate({query}: {query: string}) {
console.log('onAskForCreate', query);
showModalChoose.value = false;
showModalCreate.value = true;
}
function openModal() {
showModal.value = true;
showModalChoose.value = true;
}
function closeModal() {
showModal.value = false;
showModalChoose.value = false;
}
</script>

View File

@@ -1,10 +1,8 @@
<template>
<teleport to="body">
<modal
v-if="show"
@close="() => emit('close')"
:modal-dialog-class="modalDialogClass"
:show="show"
:hide-footer="false"
>
<template #header>
@@ -58,7 +56,6 @@
:item="item"
:search="search"
:type="checkUniq"
@save-form-on-the-fly="saveFormOnTheFly"
@new-prior-suggestion="newPriorSuggestion"
@update-selected="updateSelected"
/>
@@ -84,6 +81,7 @@
@save-form-on-the-fly="saveFormOnTheFly"
ref="onTheFly"
/>
-->
</div>
</div>
</template>
@@ -102,7 +100,7 @@
</template>
<script setup lang="ts">
import { ref, reactive, computed, nextTick, watch } from 'vue';
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';
@@ -130,7 +128,6 @@ import type {
type Result = OriginalResult & { addressId?: number };
interface Props {
show: boolean;
modalTitle: string;
options: SearchOptions;
suggested?: Suggestion[];
@@ -148,22 +145,19 @@ const props = withDefaults(defineProps<Props>(), {
const emit = defineEmits<{
(e: 'close'): void;
/** @deprecated use 'onPickEntities' */
(e: 'addNewPersons', payload: { selected: Suggestion[] }): void;
(e: 'onPickEntities', payload: { selected: EntitiesOrMe[] }): void;
(e: 'onAskForCreate', payload: { query: string }): void;
}>();
const searchRef = ref<HTMLInputElement | null>(null);
const onTheFly = ref<InstanceType<typeof OnTheFly> | null>(null);
watch(
() => props.show,
async (visible) => {
if (visible) {
await nextTick();
searchRef.value?.focus();
}
}
);
onMounted(() => {
// give the focus on the search bar
searchRef.value?.focus();
});
const search = reactive({
query: '' as string,
@@ -318,6 +312,8 @@ function pickEntities(): void {
emit('close');
}
/*
TODO remove this
async function saveFormOnTheFly({ type, data }: { type: string; data: Result }) {
try {
if (type === 'person') {
@@ -362,6 +358,8 @@ async function saveFormOnTheFly({ type, data }: { type: string; data: Result })
}
}
*/
defineExpose({ resetSearch });
</script>