Update ticket history interface and functionality

Reworked the ticket history interface and added new functionalities. The history interface now supports multiple patients and shows changes in patients' state. Additionally, new ticket creation is now displayed in the history line, along with the creator information. Some minor textual changes were made to reflect support for multiple patients. Implemented code cleanup and removed debug statements for a cleaner codebase.
This commit is contained in:
Julien Fastré 2024-06-03 23:25:53 +02:00
parent 166a6fde20
commit e45af94c78
Signed by: julienfastre
GPG Key ID: BDE2190974723FCB
9 changed files with 134 additions and 46 deletions

View File

@ -44,8 +44,6 @@ final readonly class SetPersonsController
$command = $this->serializer->deserialize($request->getContent(), SetPersonsCommand::class, 'json', [AbstractNormalizer::GROUPS => ['read']]);
dump($command);
return $this->registerSetPersons($command, $ticket);
}

View File

@ -68,14 +68,28 @@ export interface AddresseeState {
addressees: UserGroupOrUser[],
}
interface AddPersonEvent extends TicketHistory<"add_person", PersonHistory> {};
interface AddCommentEvent extends TicketHistory<"add_comment", Comment> {};
interface SetMotiveEvent extends TicketHistory<"set_motive", MotiveHistory> {};
export interface PersonsState {
persons: Person[]
}
export interface CreateTicketState {}
//interface AddPersonEvent extends TicketHistory<"add_person", PersonHistory> {};
export interface AddCommentEvent extends TicketHistory<"add_comment", Comment> {};
export interface SetMotiveEvent extends TicketHistory<"set_motive", MotiveHistory> {};
//interface AddAddressee extends TicketHistory<"add_addressee", AddresseeHistory> {};
//interface RemoveAddressee extends TicketHistory<"remove_addressee", AddresseeHistory> {};
interface AddresseesStateEvent extends TicketHistory<"addressees_state", AddresseeState> {};
export interface AddresseesStateEvent extends TicketHistory<"addressees_state", AddresseeState> {};
export interface CreateTicketEvent extends TicketHistory<"create_ticket", CreateTicketState> {};
export interface PersonStateEvent extends TicketHistory<"persons_state", PersonsState> {};
export type TicketHistoryLine = AddPersonEvent | AddCommentEvent | SetMotiveEvent | /*AddAddressee | RemoveAddressee | */ AddresseesStateEvent;
export type TicketHistoryLine =
/* AddPersonEvent */
CreateTicketEvent |
AddCommentEvent |
SetMotiveEvent | /*AddAddressee | RemoveAddressee | */
AddresseesStateEvent |
PersonStateEvent;
export interface Ticket {
type: "ticket_ticket",

View File

@ -0,0 +1,18 @@
<script setup lang="ts">
import {User} from "../../../../../../../ChillMainBundle/Resources/public/types";
interface TicketHistoryCreateComponentConfig {
by: User
}
const props = defineProps<TicketHistoryCreateComponentConfig>();
</script>
<template>
<p>Ticket créé par {{ props.by.text }}</p>
</template>
<style scoped lang="scss">
</style>

View File

@ -25,7 +25,7 @@
<div class="card-body row">
<ticket-history-person-component
:personHistory="history_line.data"
v-if="history_line.event_type == 'add_person'"
v-if="history_line.event_type == 'persons_state'"
/>
<ticket-history-motive-component
:motiveHistory="history_line.data"
@ -39,6 +39,10 @@
:addressees="history_line.data.addressees"
v-else-if="history_line.event_type == 'addressees_state'"
/>
<ticket-history-create-component
:by="history_line.by"
v-else-if="history_line.event_type == 'create_ticket'"
/>
</div>
</div>
</template>
@ -49,13 +53,14 @@ import { useStore } from "vuex";
// Types
import { DateTime } from "../../../../../../../ChillMainBundle/Resources/public/types";
import {Ticket, TicketHistoryLine} from "../../../types";
import {TicketHistoryLine} from "../../../types";
// Components
import TicketHistoryPersonComponent from "./TicketHistoryPersonComponent.vue";
import TicketHistoryMotiveComponent from "./TicketHistoryMotiveComponent.vue";
import TicketHistoryCommentComponent from "./TicketHistoryCommentComponent.vue";
import TicketHistoryAddresseeComponent from "./TicketHistoryAddresseeComponent.vue";
import TicketHistoryCreateComponent from "./TicketHistoryCreateComponent.vue";
import UserRenderBoxBadge from "ChillMainAssets/vuejs/_components/Entity/UserRenderBoxBadge.vue";
import {ISOToDatetime} from "../../../../../../../ChillMainBundle/Resources/public/chill/js/date";
@ -67,6 +72,7 @@ export default defineComponent({
TicketHistoryMotiveComponent,
TicketHistoryCommentComponent,
TicketHistoryAddresseeComponent,
TicketHistoryCreateComponent,
},
props: {
history: {
@ -84,10 +90,12 @@ export default defineComponent({
return "Nouveau commentaire";
case "addressees_state":
return "Attributions";
case "add_person":
case "persons_state":
return "Patients concernés";
case "set_motive":
return "Nouveau motifs";
case "create_ticket":
return "Ticket créé";
}
};

View File

@ -1,57 +1,43 @@
<template>
<div class="col-12" v-if="personHistory.createdBy">
<span class="mx-1">
{{ $t("history.user") }}
<span class="badge bg-primary m-1">
{{ personHistory.createdBy.username }}
</span>
</span>
</div>
<div class="col-12">
<span class="mx-1">
{{ $t("history.person") }}
</span>
<person-render-box
render="badge"
:key="personHistory.person.id"
:person="personHistory.person"
:options="{
addLink: true,
addId: false,
addAltNames: false,
addEntity: true,
addInfo: true,
hLevel: 3,
isMultiline: true,
isConfidential: false,
}"
/>
<ul class="persons-list">
<li v-for="person in personHistory.persons" :key="person.id">
<on-the-fly :type="person.type" :id="person.id" :buttonText="person.textAge" :displayBadge="'true' === 'true'" action="show"></on-the-fly>
</li>
</ul>
</div>
</template>
<script lang="ts">
import { PropType, defineComponent } from "vue";
// Components
import PersonRenderBox from "../../../../../../../ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonRenderBox.vue";
// Type
import { PersonHistory } from "../../../types";
import {PersonsState} from "../../../types";
import OnTheFly from "ChillMainAssets/vuejs/OnTheFly/components/OnTheFly.vue";
export default defineComponent({
name: "TicketHistoryPersonComponent",
props: {
personHistory: {
type: Object as PropType<PersonHistory>,
type: Object as PropType<PersonsState>,
required: true,
},
},
components: {
PersonRenderBox,
OnTheFly,
},
setup() {},
});
</script>
<style lang="scss" scoped></style>
<style lang="scss" scoped>
ul.persons-list {
list-style-type: none;
& > li {
display: inline-block;
margin: 0 0.15rem;
}
}
</style>

View File

@ -38,7 +38,7 @@ const messages = {
user_label: "Ajouter un patient",
},
banner: {
concerned_patient: "Patient concerné",
concerned_patient: "Patients concernés",
speaker: "Attribué à",
open: "Ouvert",
since: "Depuis {time}",

View File

@ -12,12 +12,14 @@ export const moduleTicket: Module<State, RootState> = {
state: () => ({
ticket: {} as Ticket,
action_icons: {
// TODO cleanup those keys
add_person: "fa fa-eyedropper",
add_comment: "fa fa-comment",
set_motive: "fa fa-paint-brush",
//add_addressee: "fa fa-paper-plane",
addressees_state: "fa fa-paper-plane",
set_persons: "fa fa-eyedropper",
persons_state: "fa fa-eyedropper",
},
toto: "toto",
}),
@ -37,7 +39,7 @@ export const moduleTicket: Module<State, RootState> = {
(result, item) => {
const { datetime } = item.at;
if (
!["add_addressee", "remove_addressee"].includes(
!["add_addressee", "remove_addressee", "add_person"].includes(
item.event_type
)
) {

View File

@ -46,7 +46,7 @@
<div class="wrap-list">
{% if ticket.persons|length > 0 %}
<div class="wl-row">
<div class="wl-col title"><h3 class="mb-2">Patient concerné</h3></div>
<div class="wl-col title"><h3 class="mb-2">Patients concernés</h3></div>
<div class="wl-col list">
{% for p in ticket.persons %}
{% include '@ChillMain/OnTheFly/_insert_vue_onthefly.html.twig' with {

View File

@ -13,6 +13,7 @@ namespace Chill\TicketBundle\Serializer\Normalizer;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\UserGroup;
use Chill\PersonBundle\Entity\Person;
use Chill\TicketBundle\Entity\AddresseeHistory;
use Chill\TicketBundle\Entity\Comment;
use Chill\TicketBundle\Entity\MotiveHistory;
@ -107,8 +108,19 @@ final class TicketNormalizer implements NormalizerInterface, NormalizerAwareInte
),
*/
...$this->addresseesStates($ticket),
...$this->personStates($ticket),
];
if (null !== $ticket->getCreatedBy() && null !== $ticket->getCreatedAt()) {
$events[] =
[
'event_type' => 'create_ticket',
'at' => $ticket->getCreatedAt()->sub(new \DateInterval('PT1S')), // TODO hack to avoid collision with creation of the ticket event,
'by' => $ticket->getCreatedBy(),
'data' => [],
];
}
usort(
$events,
static function (array $a, array $b): int {
@ -176,4 +188,54 @@ final class TicketNormalizer implements NormalizerInterface, NormalizerAwareInte
return $steps;
}
private function personStates(Ticket $ticket): array
{
/** @var array{string, array{added: list<PersonHistory>, removed: list<PersonHistory>}} $changes */
$changes = [];
$dateFormat = 'Y-m-d-m-Y-H-i-s';
foreach ($ticket->getPersonHistories() as $history) {
$changes[$history->getStartDate()->format($dateFormat)]['added'][] = $history;
if (null !== $history->getEndDate()) {
$changes[$history->getEndDate()->format($dateFormat)]['removed'][] = $history;
}
}
ksort($changes);
$currents = [];
$steps = [];
foreach ($changes as $change) {
$historiesAdded = $change['added'] ?? [];
$historiesRemoved = $change['removed'] ?? [];
if (0 < count($historiesAdded)) {
$at = $historiesAdded[0]->getStartDate();
$by = $historiesAdded[0]->getCreatedBy();
} elseif (0 < count($historiesRemoved)) {
$at = $historiesRemoved[0]->getEndDate();
$by = $historiesRemoved[0]->getRemovedBy();
} else {
throw new \LogicException('it should have at least one history');
}
$removed = array_map(fn (PersonHistory $history) => $history->getPerson(), $historiesRemoved);
$currents = array_filter($currents, fn (Person $a) => !in_array($a, $removed, true));
foreach ($historiesAdded as $history) {
$currents[] = $history->getPerson();
}
$steps[] = [
'event_type' => 'persons_state',
'at' => $at,
'by' => $by,
'data' => [
'persons' => array_values($currents),
],
];
}
return $steps;
}
}