mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-12 21:34:25 +00:00
Add feature to set concerned persons in a ticket
This commit adds the functionality to set and change the concerned persons in a ticket within the ChillTicketBundle. New vuejs components, serializers, and store modules have been introduced to achieve this. Moreover, necessary changes have been made in existing components and store index to support this functionality.
This commit is contained in:
parent
631f047338
commit
166a6fde20
@ -44,6 +44,8 @@ final readonly class SetPersonsController
|
||||
|
||||
$command = $this->serializer->deserialize($request->getContent(), SetPersonsCommand::class, 'json', [AbstractNormalizer::GROUPS => ['read']]);
|
||||
|
||||
dump($command);
|
||||
|
||||
return $this->registerSetPersons($command, $ticket);
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<form @submit.prevent="submitAction">
|
||||
<form v-if="activeTab !== 'set_persons'" @submit.prevent="submitAction">
|
||||
<add-comment-component
|
||||
v-model="content"
|
||||
v-if="activeTab === 'add_comment'"
|
||||
@ -37,12 +37,15 @@
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button class="btn btn-create" type="submit">
|
||||
<button class="btn btn-save" type="submit">
|
||||
{{ $t("ticket.save") }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</form>
|
||||
<template v-else>
|
||||
<persons-selector-component @closeRequested="closeAllActions()" />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-ticket-main">
|
||||
@ -107,6 +110,24 @@
|
||||
{{ $t("add_addressee.title") }}
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item p-2">
|
||||
<button
|
||||
type="button"
|
||||
:class="`btn ${
|
||||
activeTab === 'set_persons'
|
||||
? 'btn-primary'
|
||||
: 'btn-light'
|
||||
}`"
|
||||
@click="
|
||||
activeTab === 'set_persons'
|
||||
? (activeTab = '')
|
||||
: (activeTab = 'set_persons')
|
||||
"
|
||||
>
|
||||
<i :class="actionIcons['set_persons']"></i>
|
||||
Patients concernés
|
||||
</button>
|
||||
</li>
|
||||
|
||||
<li class="nav-item p-2">
|
||||
<button
|
||||
@ -140,10 +161,12 @@ import { Comment, Motive, Ticket } from "../../../types";
|
||||
import MotiveSelectorComponent from "./MotiveSelectorComponent.vue";
|
||||
import AddresseeSelectorComponent from "./AddresseeSelectorComponent.vue";
|
||||
import AddCommentComponent from "./AddCommentComponent.vue";
|
||||
import PersonsSelectorComponent from "./PersonsSelectorComponent.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ActionToolbarComponent",
|
||||
components: {
|
||||
PersonsSelectorComponent,
|
||||
AddCommentComponent,
|
||||
MotiveSelectorComponent,
|
||||
AddresseeSelectorComponent,
|
||||
@ -153,7 +176,7 @@ export default defineComponent({
|
||||
const { t } = useI18n();
|
||||
const toast = inject("toast") as any;
|
||||
const activeTab = ref(
|
||||
"" as "" | "add_comment" | "set_motive" | "add_addressee"
|
||||
"" as "" | "add_comment" | "set_motive" | "add_addressee" | "set_persons"
|
||||
);
|
||||
|
||||
const ticket = computed(() => store.getters.getTicket as Ticket);
|
||||
@ -239,6 +262,9 @@ export default defineComponent({
|
||||
alert("Sera disponible plus tard");
|
||||
}
|
||||
|
||||
const closeAllActions = function() {
|
||||
activeTab.value = "";
|
||||
}
|
||||
|
||||
return {
|
||||
actionIcons: ref(store.getters.getActionIcons),
|
||||
@ -254,6 +280,7 @@ export default defineComponent({
|
||||
handleClick,
|
||||
hasReturnPath,
|
||||
returnPath,
|
||||
closeAllActions,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
@ -0,0 +1,131 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import {useStore} from "vuex";
|
||||
import AddPersons from "ChillPersonAssets/vuejs/_components/AddPersons.vue";
|
||||
import {computed, inject, reactive} from "vue";
|
||||
import {Ticket} from "../../../types";
|
||||
import {Person} from "../../../../../../../ChillPersonBundle/Resources/public/types"
|
||||
import OnTheFly from "ChillMainAssets/vuejs/OnTheFly/components/OnTheFly.vue";
|
||||
import {ToastPluginApi} from "vue-toast-notification";
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'closeRequested'): void
|
||||
}>()
|
||||
|
||||
const store = useStore();
|
||||
const toast = inject("toast") as ToastPluginApi;
|
||||
const ticket = computed<Ticket>(() => store.getters.getTicket);
|
||||
const persons = computed(() => ticket.value.currentPersons);
|
||||
|
||||
const addPersonsOptions = {
|
||||
uniq: false,
|
||||
type: ['person'],
|
||||
priority: null,
|
||||
button: {
|
||||
class: 'btn-submit',
|
||||
},
|
||||
};
|
||||
|
||||
const added: Person[] = reactive([]);
|
||||
const removed: Person[] = reactive([]);
|
||||
|
||||
const computeCurrentPersons = (initial: Person[], added: Person[], removed: Person[]): Person[] => {
|
||||
for (let p of added) {
|
||||
if (initial.findIndex((element) => element.id === p.id) === -1) {
|
||||
initial.push(p);
|
||||
}
|
||||
}
|
||||
|
||||
return initial.filter((p) => removed.findIndex((element) => element.id === p.id) === -1);
|
||||
}
|
||||
|
||||
const currentPersons = computed((): Person[] => {
|
||||
return computeCurrentPersons(persons.value, added, removed);
|
||||
})
|
||||
|
||||
const removePerson = (p: Person) => {
|
||||
removed.push(p);
|
||||
}
|
||||
|
||||
const addNewEntity = (n: {modal: {showModal: boolean}, selected: Array<{result: Person}>}) => {
|
||||
n.modal.showModal = false;
|
||||
for (let p of n.selected) {
|
||||
added.push(p.result);
|
||||
}
|
||||
}
|
||||
|
||||
const save = async function(): Promise<void> {
|
||||
try {
|
||||
await store.dispatch("setPersons", { persons: computeCurrentPersons(persons.value, added, removed) });
|
||||
toast.success("Patients concernés sauvegardés");
|
||||
} catch (e: any) {
|
||||
console.error("error while saving", e);
|
||||
toast.error((e as Error).message);
|
||||
return Promise.resolve();
|
||||
}
|
||||
emit("closeRequested");
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<ul v-if="currentPersons.length > 0" class="person-list">
|
||||
<li v-for="person in currentPersons" :key="person.id">
|
||||
<on-the-fly :type="person.type" :id="person.id" :buttonText="person.textAge" :displayBadge="'true' === 'true'" action="show"></on-the-fly>
|
||||
<button type="button" class="btn btn-delete remove-person" @click="removePerson(person)"></button>
|
||||
</li>
|
||||
</ul>
|
||||
<p v-else class="chill-no-data-statement">Aucun patient</p>
|
||||
</div>
|
||||
<ul class="record_actions">
|
||||
<li class="cancel">
|
||||
<button
|
||||
class="btn btn-cancel"
|
||||
type="button"
|
||||
@click="emit('closeRequested')"
|
||||
>
|
||||
{{ $t("ticket.cancel") }}
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<add-persons
|
||||
:options="addPersonsOptions"
|
||||
key="add-person-ticket"
|
||||
buttonTitle="set_persons.user_label"
|
||||
modalTitle="set_persons.user_label"
|
||||
ref="addPersons"
|
||||
@addNewPersons="addNewEntity"
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<button class="btn btn-save" type="submit" @click.prevent="save">
|
||||
{{ $t("ticket.save") }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
ul.person-list {
|
||||
list-style-type: none;
|
||||
|
||||
& > li {
|
||||
display: inline-block;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 6px;
|
||||
|
||||
button.remove-person {
|
||||
opacity: 10%;
|
||||
}
|
||||
}
|
||||
|
||||
& > li:hover {
|
||||
border: 1px solid white;
|
||||
|
||||
button.remove-person {
|
||||
opacity: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -33,6 +33,10 @@ const messages = {
|
||||
success: "Attribution effectuée",
|
||||
error: "Aucun destinataire sélectionné",
|
||||
},
|
||||
set_persons: {
|
||||
title: "Patients concernés",
|
||||
user_label: "Ajouter un patient",
|
||||
},
|
||||
banner: {
|
||||
concerned_patient: "Patient concerné",
|
||||
speaker: "Attribué à",
|
||||
|
@ -3,12 +3,14 @@ import { State as MotiveStates, moduleMotive } from "./modules/motive";
|
||||
import { State as TicketStates, moduleTicket } from "./modules/ticket";
|
||||
import { State as CommentStates, moduleComment } from "./modules/comment";
|
||||
import { State as AddresseeStates, moduleAddressee } from "./modules/addressee";
|
||||
import { State as PersonsState, modulePersons} from "./modules/persons";
|
||||
|
||||
export type RootState = {
|
||||
motive: MotiveStates;
|
||||
ticket: TicketStates;
|
||||
comment: CommentStates;
|
||||
addressee: AddresseeStates;
|
||||
persons: PersonsState;
|
||||
};
|
||||
|
||||
export const store = createStore<RootState>({
|
||||
@ -17,5 +19,6 @@ export const store = createStore<RootState>({
|
||||
ticket: moduleTicket,
|
||||
comment: moduleComment,
|
||||
addressee: moduleAddressee,
|
||||
persons: modulePersons,
|
||||
},
|
||||
});
|
||||
|
@ -0,0 +1,32 @@
|
||||
import {
|
||||
makeFetch,
|
||||
} from "../../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
|
||||
import { Person } from "../../../../../../../../ChillPersonBundle/Resources/public/types";
|
||||
import { Module } from "vuex";
|
||||
import { RootState } from "..";
|
||||
import {Ticket} from "../../../../types";
|
||||
|
||||
export interface State {};
|
||||
|
||||
export const modulePersons: Module<State, RootState> = {
|
||||
actions: {
|
||||
async setPersons(
|
||||
{ commit, rootState: RootState },
|
||||
payload: { persons: Person[] }
|
||||
) {
|
||||
const persons = payload.persons.map((person: Person) => ({id: person.id, type: person.type}));
|
||||
try {
|
||||
const result: Ticket = await makeFetch(
|
||||
"POST",
|
||||
`/api/1.0/ticket/${RootState.ticket.ticket.id}/persons/set`,
|
||||
{ persons },
|
||||
);
|
||||
commit("setTicket", result);
|
||||
|
||||
return Promise.resolve();
|
||||
} catch (e: any) {
|
||||
throw e.name;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@ export const moduleTicket: Module<State, RootState> = {
|
||||
set_motive: "fa fa-paint-brush",
|
||||
//add_addressee: "fa fa-paper-plane",
|
||||
addressees_state: "fa fa-paper-plane",
|
||||
set_persons: "fa fa-eyedropper",
|
||||
},
|
||||
toto: "toto",
|
||||
}),
|
||||
|
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\TicketBundle\Serializer\Normalizer;
|
||||
|
||||
use Chill\PersonBundle\Entity\Person;
|
||||
use Chill\TicketBundle\Action\Ticket\SetPersonsCommand;
|
||||
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
|
||||
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
|
||||
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait;
|
||||
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
|
||||
|
||||
class SetPersonsCommandDenormalizer implements DenormalizerInterface, DenormalizerAwareInterface
|
||||
{
|
||||
use DenormalizerAwareTrait;
|
||||
|
||||
public function denormalize($data, string $type, ?string $format = null, array $context = [])
|
||||
{
|
||||
if (null === $data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!array_key_exists('persons', $data)) {
|
||||
throw new UnexpectedValueException("key 'persons' does exists");
|
||||
}
|
||||
|
||||
if (!is_array($data['persons'])) {
|
||||
throw new UnexpectedValueException("key 'persons' must be an array");
|
||||
}
|
||||
|
||||
$persons = [];
|
||||
foreach ($data['persons'] as $person) {
|
||||
$persons[] = $this->denormalizer->denormalize($person, Person::class, $format, $context);
|
||||
}
|
||||
|
||||
return new SetPersonsCommand($persons);
|
||||
}
|
||||
|
||||
public function supportsDenormalization($data, string $type, ?string $format = null)
|
||||
{
|
||||
return SetPersonsCommand::class === $type;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user