mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-08-01 21:47:44 +00:00
Créer un composant pour afficher une liste des tickets
This commit is contained in:
parent
c5e6122d2c
commit
b7c9b60744
@ -10,9 +10,11 @@
|
||||
>
|
||||
<div class="modal-dialog" :class="modalDialogClass || {}">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<div
|
||||
class="modal-header d-flex justify-content-between align-items-center"
|
||||
>
|
||||
<slot name="header"></slot>
|
||||
<button class="close btn" @click="emits('close')">
|
||||
<button class="close btn ms-auto" @click="emits('close')">
|
||||
<i class="fa fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -132,6 +132,7 @@ interface BaseTicket<
|
||||
type: "ticket_ticket";
|
||||
id: number;
|
||||
externalRef: string;
|
||||
createdAt: DateTime | null;
|
||||
currentAddressees: UserGroupOrUser[];
|
||||
currentPersons: Person[];
|
||||
currentMotive: null | Motive;
|
||||
@ -146,7 +147,6 @@ export interface TicketSimple extends BaseTicket<"ticket_ticket:simple"> {
|
||||
|
||||
export interface Ticket extends BaseTicket<"ticket_ticket:extended"> {
|
||||
type_extended: "ticket_ticket:extended";
|
||||
createdAt: DateTime | null;
|
||||
createdBy: User | null;
|
||||
updatedAt: DateTime | null;
|
||||
updatedBy: User | null;
|
||||
|
@ -1,11 +1,12 @@
|
||||
<template>
|
||||
<banner-component :ticket="ticket" />
|
||||
<div class="container-xxl pt-1" style="padding-bottom: 55px">
|
||||
<ticket-selector-component :tickets="[]" />
|
||||
<previous-tickets-component />
|
||||
<ticket-history-list-component :history="ticketHistory" />
|
||||
</div>
|
||||
<action-toolbar-component />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useToast } from "vue-toast-notification";
|
||||
import { computed, onMounted } from "vue";
|
||||
@ -15,7 +16,7 @@ import { useStore } from "vuex";
|
||||
import { Ticket } from "../../types";
|
||||
|
||||
// Components
|
||||
import TicketSelectorComponent from "./components/TicketSelectorComponent.vue";
|
||||
import PreviousTicketsComponent from "./components/PreviousTicketsComponent.vue";
|
||||
import TicketHistoryListComponent from "./components/TicketHistoryListComponent.vue";
|
||||
import ActionToolbarComponent from "./components/ActionToolbarComponent.vue";
|
||||
import BannerComponent from "./components/BannerComponent.vue";
|
||||
@ -27,7 +28,7 @@ store.commit("setTicket", JSON.parse(window.initialTicket) as Ticket);
|
||||
|
||||
const ticket = computed(() => store.getters.getTicket as Ticket);
|
||||
|
||||
const ticketHistory = computed(() => store.getters.getDistinctAddressesHistory);
|
||||
const ticketHistory = computed(() => store.getters.getTicketHistory);
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
|
@ -27,7 +27,7 @@
|
||||
</div>
|
||||
|
||||
<form @submit.prevent="submitAction">
|
||||
<add-comment-component
|
||||
<comment-editor-component
|
||||
v-model="content"
|
||||
v-if="activeTab === 'add_comment'"
|
||||
/>
|
||||
@ -53,12 +53,21 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<caller-selector-component v-model="caller" :suggested="[]" />
|
||||
<persons-selector-component
|
||||
v-model="caller"
|
||||
:suggested="[]"
|
||||
:multiple="false"
|
||||
:types="['person', 'thirdparty']"
|
||||
:label="trans(CHILL_TICKET_TICKET_SET_PERSONS_CALLER_LABEL)"
|
||||
/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<persons-selector-component
|
||||
v-model="persons"
|
||||
:suggested="suggestedPersons"
|
||||
:multiple="true"
|
||||
:types="['person']"
|
||||
:label="trans(CHILL_TICKET_TICKET_SET_PERSONS_USER_LABEL)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -82,15 +91,14 @@
|
||||
<li v-else class="nav-item p-2 go-back">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-link p-0"
|
||||
style="font-size: 1.5rem; line-height: 1; color: #888"
|
||||
class="btn btn-light"
|
||||
@click="closeAllActions"
|
||||
aria-label="Fermer"
|
||||
title="Fermer"
|
||||
>
|
||||
<span aria-hidden="true">×</span>
|
||||
<i class="fa fa-arrow-left"></i>
|
||||
{{ trans(CHILL_TICKET_TICKET_ACTIONS_TOOLBAR_CANCEL) }}
|
||||
</button>
|
||||
>
|
||||
</li>
|
||||
<li v-for="btn in actionButtons" :key="btn.key" class="nav-item p-2">
|
||||
<button
|
||||
@ -130,11 +138,10 @@ import { useStore } from "vuex";
|
||||
import { useToast } from "vue-toast-notification";
|
||||
|
||||
// Component
|
||||
import MotiveSelectorComponent from "./MotiveSelectorComponent.vue";
|
||||
import AddresseeSelectorComponent from "./AddresseeSelectorComponent.vue";
|
||||
import AddCommentComponent from "./AddCommentComponent.vue";
|
||||
import PersonsSelectorComponent from "./PersonsSelectorComponent.vue";
|
||||
import CallerSelectorComponent from "./CallerSelectorComponent.vue";
|
||||
import MotiveSelectorComponent from "./Motive/MotiveSelectorComponent.vue";
|
||||
import AddresseeSelectorComponent from "./Addressee/AddresseeSelectorComponent.vue";
|
||||
import CommentEditorComponent from "./Comment/CommentEditorComponent.vue";
|
||||
import PersonsSelectorComponent from "./Person/PersonsSelectorComponent.vue";
|
||||
|
||||
// Translations
|
||||
import {
|
||||
@ -152,6 +159,8 @@ import {
|
||||
CHILL_TICKET_TICKET_SET_PERSONS_TITLE_CALLER,
|
||||
CHILL_TICKET_TICKET_SET_PERSONS_TITLE,
|
||||
CHILL_TICKET_TICKET_SET_PERSONS_SUCCESS,
|
||||
CHILL_TICKET_TICKET_SET_PERSONS_USER_LABEL,
|
||||
CHILL_TICKET_TICKET_SET_PERSONS_CALLER_LABEL,
|
||||
CHILL_TICKET_TICKET_ACTIONS_TOOLBAR_SAVE,
|
||||
CHILL_TICKET_TICKET_ACTIONS_TOOLBAR_CLOSE,
|
||||
CHILL_TICKET_TICKET_ACTIONS_TOOLBAR_CLOSE_SUCCESS,
|
||||
@ -159,6 +168,7 @@ import {
|
||||
CHILL_TICKET_TICKET_ACTIONS_TOOLBAR_REOPEN,
|
||||
CHILL_TICKET_TICKET_ACTIONS_TOOLBAR_REOPEN_SUCCESS,
|
||||
CHILL_TICKET_TICKET_ACTIONS_TOOLBAR_REOPEN_ERROR,
|
||||
CHILL_TICKET_TICKET_ACTIONS_TOOLBAR_CANCEL,
|
||||
} from "translator";
|
||||
|
||||
// Types
|
||||
@ -220,7 +230,7 @@ const isOpen = computed(() => store.getters.isOpen);
|
||||
const motives = computed(() => store.getters.getMotives as Motive[]);
|
||||
const userGroups = computed(() => store.getters.getUserGroups as UserGroup[]);
|
||||
const suggestedPersons = computed(() => store.getters.getPersons as Person[]);
|
||||
console.log("suggestedPersons", suggestedPersons.value);
|
||||
|
||||
const hasReturnPath = computed((): boolean => {
|
||||
const params = new URL(document.location.toString()).searchParams;
|
||||
return params.has("returnPath");
|
||||
@ -291,6 +301,7 @@ async function submitAction() {
|
||||
await store.dispatch("setPersons", {
|
||||
persons: persons.value,
|
||||
});
|
||||
await store.dispatch("fetchTicketsByPerson");
|
||||
activeTab.value = "";
|
||||
toast.success(trans(CHILL_TICKET_TICKET_SET_PERSONS_SUCCESS));
|
||||
break;
|
||||
@ -351,6 +362,6 @@ div.footer-ticket-details {
|
||||
.fixed-bottom {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
}
|
||||
</style>
|
||||
|
@ -37,7 +37,7 @@ import {
|
||||
User,
|
||||
UserGroup,
|
||||
UserGroupOrUser,
|
||||
} from "../../../../../../../ChillMainBundle/Resources/public/types";
|
||||
} from "../../../../../../../../ChillMainBundle/Resources/public/types";
|
||||
|
||||
const props = defineProps<{ addressees: UserGroupOrUser[] }>();
|
||||
|
@ -3,12 +3,10 @@
|
||||
<div class="container-xxl text-primary">
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-sm-12 ps-md-5 ps-xxl-0">
|
||||
<h1 v-if="ticket.currentMotive">
|
||||
#{{ ticket.id }} {{ ticket.currentMotive.label.fr }}
|
||||
<h1>
|
||||
{{ getTicketTitle(ticket) }}
|
||||
</h1>
|
||||
<p class="chill-no-data-statement" v-else>
|
||||
{{ trans(CHILL_TICKET_TICKET_BANNER_NO_MOTIVE) }}
|
||||
</p>
|
||||
|
||||
<h2 v-if="ticket.currentPersons.length">
|
||||
{{ ticket.currentPersons.map((person) => person.text).join(", ") }}
|
||||
</h2>
|
||||
@ -16,7 +14,7 @@
|
||||
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<div class="d-flex justify-content-end">
|
||||
<toggle-flags />
|
||||
<emergency-toggle-component />
|
||||
|
||||
<span
|
||||
class="badge text-bg-chill-green text-white"
|
||||
@ -49,37 +47,42 @@
|
||||
<Teleport to="#header-ticket-details">
|
||||
<div class="container-xxl">
|
||||
<div class="row justify-content-between">
|
||||
<div class="col-md-4 col-sm-12 d-flex flex-column align-items-start">
|
||||
<div
|
||||
class="col-md-4 col-sm-12 d-flex flex-column align-items-start"
|
||||
v-if="ticket.caller"
|
||||
>
|
||||
<h3 class="text-primary">
|
||||
{{ trans(CHILL_TICKET_TICKET_BANNER_CALLER) }}
|
||||
{{
|
||||
trans(CHILL_TICKET_TICKET_BANNER_CALLER, {
|
||||
count: ticket.caller ? 1 : 0,
|
||||
})
|
||||
}}
|
||||
</h3>
|
||||
<on-the-fly
|
||||
v-if="ticket.caller"
|
||||
:key="ticket.caller.id"
|
||||
:type="ticket.caller.type"
|
||||
:id="ticket.caller.id"
|
||||
:buttonText="ticket.caller.text"
|
||||
:displayBadge="'true' === 'true'"
|
||||
action="show"
|
||||
></on-the-fly>
|
||||
<person-component :entities="[ticket.caller] as Person[]" />
|
||||
</div>
|
||||
<div class="col-md-4 col-sm-12 d-flex flex-column align-items-start">
|
||||
<div
|
||||
class="col-md-4 col-sm-12 d-flex flex-column align-items-start"
|
||||
v-if="ticket.currentPersons.length"
|
||||
>
|
||||
<h3 class="text-primary">
|
||||
{{ trans(CHILL_TICKET_TICKET_BANNER_CONCERNED_USAGER) }}
|
||||
{{
|
||||
trans(CHILL_TICKET_TICKET_BANNER_PERSON, {
|
||||
count: ticket.currentPersons.length,
|
||||
})
|
||||
}}
|
||||
</h3>
|
||||
<on-the-fly
|
||||
v-for="person in ticket.currentPersons"
|
||||
:key="person.id"
|
||||
:type="person.type"
|
||||
:id="person.id"
|
||||
:buttonText="person.textAge"
|
||||
:displayBadge="'true' === 'true'"
|
||||
action="show"
|
||||
></on-the-fly>
|
||||
<person-component :entities="ticket.currentPersons" />
|
||||
</div>
|
||||
<div class="col-md-4 col-sm-12 d-flex flex-column align-items-start">
|
||||
<div
|
||||
class="col-md-4 col-sm-12 d-flex flex-column align-items-start"
|
||||
v-if="ticket.currentAddressees.length"
|
||||
>
|
||||
<h3 class="text-primary">
|
||||
{{ trans(CHILL_TICKET_TICKET_BANNER_SPEAKER) }}
|
||||
{{
|
||||
trans(CHILL_TICKET_TICKET_BANNER_SPEAKER, {
|
||||
count: ticket.currentAddressees.length,
|
||||
})
|
||||
}}
|
||||
</h3>
|
||||
<addressee-component :addressees="ticket.currentAddressees" />
|
||||
</div>
|
||||
@ -99,92 +102,41 @@
|
||||
import { ref, computed } from "vue";
|
||||
|
||||
// Components
|
||||
import AddresseeComponent from "./AddresseeComponent.vue";
|
||||
import ToggleFlags from "./ToggleFlags.vue";
|
||||
import OnTheFly from "ChillMainAssets/vuejs/OnTheFly/components/OnTheFly.vue";
|
||||
import AddresseeComponent from "./Addressee/AddresseeComponent.vue";
|
||||
import EmergencyToggleComponent from "./Emergency/EmergencyToggleComponent.vue";
|
||||
import PersonComponent from "./Person/PersonComponent.vue";
|
||||
|
||||
// Types
|
||||
import { Ticket } from "../../../types";
|
||||
import { ISOToDatetime } from "../../../../../../../ChillMainBundle/Resources/public/chill/js/date";
|
||||
|
||||
// Translations
|
||||
import {
|
||||
trans,
|
||||
CHILL_TICKET_TICKET_BANNER_NO_MOTIVE,
|
||||
CHILL_TICKET_TICKET_BANNER_OPEN,
|
||||
CHILL_TICKET_TICKET_BANNER_CLOSED,
|
||||
CHILL_TICKET_TICKET_BANNER_SINCE,
|
||||
CHILL_TICKET_TICKET_BANNER_CONCERNED_USAGER,
|
||||
CHILL_TICKET_TICKET_BANNER_SPEAKER,
|
||||
CHILL_TICKET_TICKET_BANNER_DAYS,
|
||||
CHILL_TICKET_TICKET_BANNER_HOURS,
|
||||
CHILL_TICKET_TICKET_BANNER_MINUTES,
|
||||
CHILL_TICKET_TICKET_BANNER_SECONDS,
|
||||
CHILL_TICKET_TICKET_BANNER_AND,
|
||||
CHILL_TICKET_TICKET_BANNER_CALLER,
|
||||
CHILL_TICKET_TICKET_BANNER_PERSON,
|
||||
} from "translator";
|
||||
|
||||
// Store
|
||||
import { useStore } from "vuex";
|
||||
import { Person } from "ChillPersonAssets/types";
|
||||
import { getTicketTitle } from "../utils/utils";
|
||||
|
||||
const props = defineProps<{
|
||||
defineProps<{
|
||||
ticket: Ticket;
|
||||
}>();
|
||||
|
||||
const store = useStore();
|
||||
const today = ref(new Date());
|
||||
const createdAt = ref(props.ticket.createdAt);
|
||||
|
||||
setInterval(() => {
|
||||
today.value = new Date();
|
||||
}, 5000);
|
||||
|
||||
const isOpen = computed(() => store.getters.isOpen);
|
||||
|
||||
const since = computed(() => {
|
||||
if (createdAt.value == null) {
|
||||
return "";
|
||||
}
|
||||
const date = ISOToDatetime(createdAt.value.datetime);
|
||||
|
||||
if (date == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const timeDiff = Math.abs(today.value.getTime() - date.getTime());
|
||||
const daysDiff = Math.floor(timeDiff / (1000 * 3600 * 24));
|
||||
const hoursDiff = Math.floor((timeDiff % (1000 * 3600 * 24)) / (1000 * 3600));
|
||||
const minutesDiff = Math.floor((timeDiff % (1000 * 3600)) / (1000 * 60));
|
||||
const secondsDiff = Math.floor((timeDiff % (1000 * 60)) / 1000);
|
||||
|
||||
// On construit la liste des parties à afficher
|
||||
const parts: string[] = [];
|
||||
if (daysDiff > 0) {
|
||||
parts.push(trans(CHILL_TICKET_TICKET_BANNER_DAYS, { count: daysDiff }));
|
||||
}
|
||||
if (hoursDiff > 0 || daysDiff > 0) {
|
||||
parts.push(trans(CHILL_TICKET_TICKET_BANNER_HOURS, { count: hoursDiff }));
|
||||
}
|
||||
if (minutesDiff > 0 || hoursDiff > 0 || daysDiff > 0) {
|
||||
parts.push(
|
||||
trans(CHILL_TICKET_TICKET_BANNER_MINUTES, { count: minutesDiff }),
|
||||
);
|
||||
}
|
||||
if (parts.length === 0) {
|
||||
return trans(CHILL_TICKET_TICKET_BANNER_SECONDS, {
|
||||
count: secondsDiff,
|
||||
});
|
||||
}
|
||||
if (parts.length > 1) {
|
||||
const last = parts.pop();
|
||||
return (
|
||||
parts.join(", ") +
|
||||
" " +
|
||||
trans(CHILL_TICKET_TICKET_BANNER_AND) +
|
||||
" " +
|
||||
last
|
||||
);
|
||||
}
|
||||
return parts[0];
|
||||
return store.getters.getSinceCreated(today.value);
|
||||
});
|
||||
</script>
|
||||
|
@ -1,85 +0,0 @@
|
||||
<template>
|
||||
<pick-entity
|
||||
uniqid="ticket-person-selector"
|
||||
:types="['person', 'thirdparty']"
|
||||
:picked="selectedEntity ? [selectedEntity] : []"
|
||||
:suggested="suggestedValues"
|
||||
:multiple="false"
|
||||
:removable-if-set="true"
|
||||
:display-picked="true"
|
||||
:label="trans(CHILL_TICKET_TICKET_SET_PERSONS_CALLER_LABEL)"
|
||||
@add-new-entity="addNewEntity"
|
||||
@remove-entity="removeEntity"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, defineProps, defineEmits } from "vue";
|
||||
|
||||
// Components
|
||||
import PickEntity from "ChillMainAssets/vuejs/PickEntity/PickEntity.vue";
|
||||
|
||||
// Types
|
||||
import { Entities } from "ChillPersonAssets/types";
|
||||
|
||||
// Translations
|
||||
import {
|
||||
trans,
|
||||
CHILL_TICKET_TICKET_SET_PERSONS_CALLER_LABEL,
|
||||
} from "translator";
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: Entities | null;
|
||||
suggested: Entities[];
|
||||
}>();
|
||||
const emit =
|
||||
defineEmits<(event: "update:modelValue", value: Entities | null) => void>();
|
||||
|
||||
const selectedEntity = ref<Entities | null>(props.modelValue);
|
||||
const suggestedValues = ref<Entities[]>([...props.suggested]);
|
||||
|
||||
watch(
|
||||
() => [props.suggested, props.modelValue],
|
||||
() => {
|
||||
suggestedValues.value = props.suggested.filter(
|
||||
(suggested: Entities) =>
|
||||
suggested.id === selectedEntity.value?.id &&
|
||||
suggested.type === selectedEntity.value?.type,
|
||||
);
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
|
||||
function addNewEntity({ entity }: { entity: Entities }) {
|
||||
selectedEntity.value = entity;
|
||||
emit("update:modelValue", selectedEntity.value);
|
||||
}
|
||||
|
||||
function removeEntity() {
|
||||
selectedEntity.value = null;
|
||||
emit("update:modelValue", null);
|
||||
}
|
||||
</script>
|
||||
<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>
|
@ -11,7 +11,7 @@ import { marked } from "marked";
|
||||
import DOMPurify from "dompurify";
|
||||
|
||||
// Types
|
||||
import { Comment } from "../../../types";
|
||||
import { Comment } from "../../../../types";
|
||||
|
||||
defineProps<{ commentHistory: Comment }>();
|
||||
|
@ -1,12 +1,15 @@
|
||||
<template>
|
||||
<div class="col-12 fw-bolder">
|
||||
{{ motiveHistory.motive.label.fr }}
|
||||
{{ localizeTranslatableString(motiveHistory.motive.label) }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
// Types
|
||||
import { MotiveHistory } from "../../../types";
|
||||
import { MotiveHistory } from "../../../../types";
|
||||
|
||||
//Utils
|
||||
import { localizeTranslatableString } from "../../utils/utils";
|
||||
|
||||
defineProps<{ motiveHistory: MotiveHistory }>();
|
||||
</script>
|
@ -27,7 +27,7 @@ import { ref, watch } from "vue";
|
||||
import VueMultiselect from "vue-multiselect";
|
||||
|
||||
// Types
|
||||
import { Motive } from "../../../types";
|
||||
import { Motive } from "../../../../types";
|
||||
|
||||
// Translations
|
||||
import {
|
@ -28,10 +28,10 @@ defineProps<{ entities: Person[] | Thirdparty[] }>();
|
||||
<style lang="scss" scoped>
|
||||
ul.persons-list {
|
||||
list-style-type: none;
|
||||
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
& > li {
|
||||
display: inline-block;
|
||||
margin: 0 0.15rem;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,139 @@
|
||||
<template>
|
||||
<pick-entity
|
||||
uniqid="ticket-person-selector"
|
||||
:types="types"
|
||||
:picked="pickedEntities"
|
||||
:suggested="suggestedValues"
|
||||
:multiple="multiple"
|
||||
:removable-if-set="true"
|
||||
:display-picked="true"
|
||||
:label="label"
|
||||
@add-new-entity="addNewEntity"
|
||||
@remove-entity="removeEntity"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, defineProps, defineEmits, computed } from "vue";
|
||||
|
||||
// Components
|
||||
import PickEntity from "ChillMainAssets/vuejs/PickEntity/PickEntity.vue";
|
||||
|
||||
// Types
|
||||
import { Entities, EntityType } from "ChillPersonAssets/types";
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: Entities[] | Entities | null;
|
||||
suggested: Entities[];
|
||||
multiple: boolean;
|
||||
types: EntityType[];
|
||||
label: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
"update:modelValue": [value: Entities[] | Entities | null];
|
||||
}>();
|
||||
|
||||
// Valeurs par défaut
|
||||
const multiple = props.multiple;
|
||||
const types = props.types;
|
||||
const label = props.label;
|
||||
|
||||
// État local
|
||||
const selectedEntities = ref<Entities[]>(
|
||||
multiple
|
||||
? [...((props.modelValue as Entities[]) || [])]
|
||||
: props.modelValue
|
||||
? [props.modelValue as Entities]
|
||||
: [],
|
||||
);
|
||||
const suggestedValues = ref<Entities[]>([...props.suggested]);
|
||||
|
||||
// Entités sélectionnées pour le composant PickEntity
|
||||
const pickedEntities = computed(() =>
|
||||
multiple ? selectedEntities.value : selectedEntities.value.slice(0, 1),
|
||||
);
|
||||
|
||||
watch(
|
||||
() => [props.suggested, props.modelValue],
|
||||
() => {
|
||||
// Mise à jour des entités sélectionnées
|
||||
selectedEntities.value = multiple
|
||||
? [...((props.modelValue as Entities[]) || [])]
|
||||
: props.modelValue
|
||||
? [props.modelValue as Entities]
|
||||
: [];
|
||||
|
||||
// Filtrage des suggestions
|
||||
if (multiple) {
|
||||
suggestedValues.value = props.suggested.filter(
|
||||
(suggested: Entities) =>
|
||||
!(props.modelValue as Entities[])?.some(
|
||||
(selected: Entities) =>
|
||||
suggested.id === selected.id && suggested.type === selected.type,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
const currentEntity = props.modelValue as Entities | null;
|
||||
suggestedValues.value = props.suggested.filter(
|
||||
(suggested: Entities) =>
|
||||
!(
|
||||
currentEntity &&
|
||||
suggested.id === currentEntity.id &&
|
||||
suggested.type === currentEntity.type
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
|
||||
function addNewEntity({ entity }: { entity: Entities }) {
|
||||
if (multiple) {
|
||||
selectedEntities.value.push(entity);
|
||||
emit("update:modelValue", selectedEntities.value);
|
||||
} else {
|
||||
selectedEntities.value = [entity];
|
||||
emit("update:modelValue", entity);
|
||||
}
|
||||
}
|
||||
|
||||
function removeEntity({ entity }: { entity: Entities }) {
|
||||
if (multiple) {
|
||||
const index = selectedEntities.value.findIndex(
|
||||
(selectedEntity) => selectedEntity === entity,
|
||||
);
|
||||
if (index !== -1) {
|
||||
selectedEntities.value.splice(index, 1);
|
||||
}
|
||||
emit("update:modelValue", selectedEntities.value);
|
||||
} else {
|
||||
selectedEntities.value = [];
|
||||
emit("update:modelValue", null);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<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>
|
@ -1,90 +0,0 @@
|
||||
<template>
|
||||
<pick-entity
|
||||
uniqid="ticket-person-selector"
|
||||
:types="['person']"
|
||||
:picked="selectedEntities"
|
||||
:suggested="suggestedValues"
|
||||
:multiple="false"
|
||||
:removable-if-set="true"
|
||||
:display-picked="true"
|
||||
:label="trans(CHILL_TICKET_TICKET_SET_PERSONS_USER_LABEL)"
|
||||
@add-new-entity="addNewEntity"
|
||||
@remove-entity="removeEntity"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, defineProps, defineEmits } from "vue";
|
||||
|
||||
// Components
|
||||
import PickEntity from "ChillMainAssets/vuejs/PickEntity/PickEntity.vue";
|
||||
|
||||
// Types
|
||||
import { Entities } from "ChillPersonAssets/types";
|
||||
|
||||
// Translations
|
||||
import { trans, CHILL_TICKET_TICKET_SET_PERSONS_USER_LABEL } from "translator";
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: Entities[];
|
||||
suggested: Entities[];
|
||||
}>();
|
||||
const emit = defineEmits<{
|
||||
"update:modelValue": [value: Entities[]];
|
||||
}>();
|
||||
|
||||
const selectedEntities = ref<Entities[]>([...props.modelValue]);
|
||||
const suggestedValues = ref<Entities[]>([...props.suggested]);
|
||||
|
||||
watch(
|
||||
() => [props.suggested, props.modelValue],
|
||||
() => {
|
||||
suggestedValues.value = props.suggested.filter(
|
||||
(suggested: Entities) =>
|
||||
!props.modelValue.some(
|
||||
(selected: Entities) =>
|
||||
suggested.id === selected.id && suggested.type === selected.type,
|
||||
),
|
||||
);
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
);
|
||||
|
||||
function addNewEntity({ entity }: { entity: Entities }) {
|
||||
selectedEntities.value.push(entity);
|
||||
emit("update:modelValue", selectedEntities.value);
|
||||
}
|
||||
|
||||
function removeEntity({ entity }: { entity: Entities }) {
|
||||
const index = selectedEntities.value.findIndex(
|
||||
(selectedEntity) => selectedEntity === entity,
|
||||
);
|
||||
if (index !== -1) {
|
||||
selectedEntities.value.splice(index, 1);
|
||||
}
|
||||
emit("update:modelValue", selectedEntities.value);
|
||||
}
|
||||
</script>
|
||||
<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>
|
@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<div class="d-flex justify-content-end">
|
||||
<div @click="handleClick">
|
||||
<button type="button" class="btn btn-light position-relative">
|
||||
{{ trans(CHILL_TICKET_TICKET_PREVIOUS_TICKETS) }}
|
||||
|
||||
<span
|
||||
class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-chill-green"
|
||||
>
|
||||
{{ previousTickets.length }}
|
||||
<span class="visually-hidden">Tickets</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Modal for ticket list -->
|
||||
<Modal
|
||||
v-if="showPreviousTicketModal"
|
||||
:show="showPreviousTicketModal"
|
||||
modal-dialog-class="modal-lg"
|
||||
@close="closeModal"
|
||||
>
|
||||
<template #header>
|
||||
<h3 class="modal-title">
|
||||
{{
|
||||
trans(CHILL_TICKET_LIST_TITLE_PREVIOUS_TICKETS, {
|
||||
name:
|
||||
currentPersons.length > 0
|
||||
? currentPersons
|
||||
.map((person: Person) => person.text)
|
||||
.join(", ")
|
||||
: undefined,
|
||||
})
|
||||
}}
|
||||
</h3>
|
||||
</template>
|
||||
|
||||
<template #body>
|
||||
<TicketListComponent
|
||||
:tickets="previousTickets"
|
||||
@view-ticket="handleViewTicket"
|
||||
@edit-ticket="handleEditTicket"
|
||||
/>
|
||||
</template>
|
||||
</Modal>
|
||||
|
||||
<!-- Modal for ticket history -->
|
||||
<Modal
|
||||
v-if="
|
||||
showTicketHistoryModal &&
|
||||
selectedTicketId !== null &&
|
||||
previousTicketHistory
|
||||
"
|
||||
:show="showTicketHistoryModal"
|
||||
modal-dialog-class="modal-xl"
|
||||
@close="closeHistoryModal"
|
||||
>
|
||||
<template #header>
|
||||
<h3 class="modal-title">
|
||||
{{ getTicketTitle(previousTicketDetails) }}
|
||||
</h3>
|
||||
</template>
|
||||
|
||||
<template #body>
|
||||
<TicketHistoryListComponent
|
||||
v-if="previousTicketHistory.length > 0"
|
||||
:history="previousTicketHistory"
|
||||
/>
|
||||
<div v-else class="text-center p-4">
|
||||
<div class="spinner-border" role="status">
|
||||
<span class="visually-hidden">Chargement...</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #footer>
|
||||
<button
|
||||
class="btn btn-edit"
|
||||
@click="handleEditTicket(selectedTicketId)"
|
||||
>
|
||||
{{ trans(EDIT) }}
|
||||
</button>
|
||||
</template>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
|
||||
// Components
|
||||
import Modal from "../../../../../../../ChillMainBundle/Resources/public/vuejs/_components/Modal.vue";
|
||||
import TicketListComponent from "./TicketListComponent.vue";
|
||||
import TicketHistoryListComponent from "./TicketHistoryListComponent.vue";
|
||||
|
||||
// Translations
|
||||
import {
|
||||
trans,
|
||||
CHILL_TICKET_TICKET_PREVIOUS_TICKETS,
|
||||
CHILL_TICKET_LIST_TITLE_PREVIOUS_TICKETS,
|
||||
EDIT,
|
||||
} from "translator";
|
||||
|
||||
// Types
|
||||
import { Person } from "ChillPersonAssets/types";
|
||||
import { TicketHistoryLine, TicketSimple } from "../../../types";
|
||||
|
||||
// Utils
|
||||
import { getTicketTitle } from "../utils/utils";
|
||||
|
||||
const store = useStore();
|
||||
const showPreviousTicketModal = ref(false);
|
||||
const showTicketHistoryModal = ref(false);
|
||||
const selectedTicketId = ref<number | null>(null);
|
||||
|
||||
const currentPersons = computed(
|
||||
() => store.getters.getCurrentPersons as Person[],
|
||||
);
|
||||
const previousTickets = computed(
|
||||
() => store.getters.getPreviousTickets as TicketSimple[],
|
||||
);
|
||||
const previousTicketHistory = computed(
|
||||
() => store.getters.getPreviousTicketHistory as TicketHistoryLine[],
|
||||
);
|
||||
const previousTicketDetails = computed(() => {
|
||||
return store.getters.getPreviousTicketDetails as TicketSimple;
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
await store.dispatch("fetchTicketsByPerson");
|
||||
} catch (error) {
|
||||
console.error("Erreur lors du chargement des tickets:", error);
|
||||
}
|
||||
});
|
||||
|
||||
function handleClick() {
|
||||
showPreviousTicketModal.value = true;
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
showPreviousTicketModal.value = false;
|
||||
}
|
||||
|
||||
async function handleViewTicket(ticketId: number) {
|
||||
selectedTicketId.value = ticketId;
|
||||
showTicketHistoryModal.value = true;
|
||||
|
||||
try {
|
||||
await store.dispatch("fetchPreviousTicketDetails", ticketId);
|
||||
} catch (error) {
|
||||
console.error("Erreur lors du chargement de l'historique:", error);
|
||||
}
|
||||
}
|
||||
|
||||
function handleEditTicket(ticketId: number) {
|
||||
window.location.href = `/fr/ticket/ticket/${ticketId}/edit`;
|
||||
}
|
||||
|
||||
function closeHistoryModal() {
|
||||
showTicketHistoryModal.value = false;
|
||||
selectedTicketId.value = null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
@ -13,36 +13,11 @@
|
||||
{{ trans(CHILL_TICKET_TICKET_BANNER_CLOSED) }}
|
||||
</template>
|
||||
</span>
|
||||
<!--
|
||||
<span
|
||||
class="text-chill-green mx-2"
|
||||
style="
|
||||
font-size: 1rem;
|
||||
max-width: 80px;
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
"
|
||||
v-if="props.new_state == 'open'"
|
||||
>
|
||||
{{ trans(CHILL_TICKET_TICKET_BANNER_OPEN) }}
|
||||
</span>
|
||||
<span
|
||||
class="text-chill-red mx-2"
|
||||
style="
|
||||
font-size: 1rem;
|
||||
max-width: 80px;
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
"
|
||||
v-else-if="props.new_state == 'closed'"
|
||||
>
|
||||
{{ trans(CHILL_TICKET_TICKET_BANNER_CLOSED) }}
|
||||
</span> -->
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// Types
|
||||
import { StateChange } from "../../../types";
|
||||
import { StateChange } from "../../../../types";
|
||||
|
||||
// Translations
|
||||
import {
|
||||
@ -50,6 +25,7 @@ import {
|
||||
CHILL_TICKET_TICKET_BANNER_OPEN,
|
||||
CHILL_TICKET_TICKET_BANNER_CLOSED,
|
||||
} from "translator";
|
||||
|
||||
const props = defineProps<StateChange>();
|
||||
</script>
|
||||
|
@ -1,18 +0,0 @@
|
||||
<template>
|
||||
<div class="col-12">
|
||||
<addressee-component :addressees="addresseeState.addressees" />
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
// Types
|
||||
import { AddresseeState } from "../../../types";
|
||||
|
||||
// Components
|
||||
import AddresseeComponent from "./AddresseeComponent.vue";
|
||||
|
||||
defineProps<{
|
||||
addresseeState: AddresseeState;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
@ -1,16 +0,0 @@
|
||||
<template>
|
||||
<p>Ticket créé par {{ props.by.text }}</p>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// Types
|
||||
import { User } from "../../../../../../../ChillMainBundle/Resources/public/types";
|
||||
|
||||
interface TicketHistoryCreateComponentConfig {
|
||||
by: User;
|
||||
}
|
||||
|
||||
const props = defineProps<TicketHistoryCreateComponentConfig>();
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
@ -13,12 +13,12 @@
|
||||
<div class="d-flex align-items-center fw-bold">
|
||||
<i :class="`${actionIcons[history_line.event_type]} me-1`"></i>
|
||||
<span>{{ explainSentence(history_line) }}</span>
|
||||
<TicketHistoryStateComponent
|
||||
<state-component
|
||||
:new_state="history_line.data.new_state"
|
||||
v-if="history_line.event_type == 'state_change'"
|
||||
/>
|
||||
<TicketHistoryEmergencyComponent
|
||||
v-if="history_line.event_type == 'emergency_change'"
|
||||
<emergency-component
|
||||
v-else-if="history_line.event_type == 'emergency_change'"
|
||||
:new_emergency="history_line.data.new_emergency"
|
||||
/>
|
||||
</div>
|
||||
@ -37,37 +37,35 @@
|
||||
<div
|
||||
class="card-body row"
|
||||
v-if="
|
||||
!['state_change', 'emergency_change'].includes(history_line.event_type)
|
||||
!['state_change', 'emergency_change', 'create_ticket'].includes(
|
||||
history_line.event_type,
|
||||
)
|
||||
"
|
||||
>
|
||||
<ticket-history-person-component
|
||||
<person-component
|
||||
:entities="history_line.data.persons"
|
||||
v-if="history_line.event_type == 'persons_state'"
|
||||
/>
|
||||
<ticket-history-person-component
|
||||
<person-component
|
||||
:entities="
|
||||
history_line.data.new_caller
|
||||
? ([history_line.data.new_caller] as Person[] | Thirdparty[])
|
||||
: []
|
||||
"
|
||||
v-if="history_line.event_type == 'set_caller'"
|
||||
v-else-if="history_line.event_type == 'set_caller'"
|
||||
/>
|
||||
<ticket-history-motive-component
|
||||
<motive-component
|
||||
:motiveHistory="history_line.data"
|
||||
v-else-if="history_line.event_type == 'set_motive'"
|
||||
/>
|
||||
<ticket-history-comment-component
|
||||
<comment-component
|
||||
:commentHistory="history_line.data"
|
||||
v-else-if="history_line.event_type == 'add_comment'"
|
||||
/>
|
||||
<ticket-history-addressee-component
|
||||
:addresseeState="history_line.data"
|
||||
<addressee-component
|
||||
: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>
|
||||
@ -82,13 +80,12 @@ import { Person } from "ChillPersonAssets/types";
|
||||
import { Thirdparty } from "src/Bundle/ChillThirdPartyBundle/Resources/public/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 TicketHistoryStateComponent from "./TicketHistoryStateComponent.vue";
|
||||
import TicketHistoryEmergencyComponent from "./TicketHistoryEmergencyComponent.vue";
|
||||
import PersonComponent from "./Person/PersonComponent.vue";
|
||||
import MotiveComponent from "./Motive/MotiveComponent.vue";
|
||||
import CommentComponent from "./Comment/CommentComponent.vue";
|
||||
import AddresseeComponent from "./Addressee/AddresseeComponent.vue";
|
||||
import StateComponent from "./State/StateComponent.vue";
|
||||
import EmergencyComponent from "./Emergency/EmergencyComponent.vue";
|
||||
|
||||
import UserRenderBoxBadge from "ChillMainAssets/vuejs/_components/Entity/UserRenderBoxBadge.vue";
|
||||
|
||||
|
@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<div class="ticket-list-container">
|
||||
<div v-if="tickets.length === 0" class="chill-no-data-statement">
|
||||
{{ trans(CHILL_TICKET_LIST_NO_TICKETS) }}
|
||||
</div>
|
||||
|
||||
<div v-else class="flex-table">
|
||||
<TicketListItemComponent
|
||||
v-for="ticket in tickets"
|
||||
:key="ticket.id"
|
||||
:ticket="ticket"
|
||||
@view-ticket="$emit('view-ticket', $event)"
|
||||
@edit-ticket="$emit('edit-ticket', $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, defineEmits } from "vue";
|
||||
|
||||
// Types
|
||||
import { TicketSimple } from "../../../types";
|
||||
|
||||
// Components
|
||||
import TicketListItemComponent from "./TicketListItemComponent.vue";
|
||||
|
||||
// Translations
|
||||
import { trans, CHILL_TICKET_LIST_NO_TICKETS } from "translator";
|
||||
|
||||
defineProps<{
|
||||
tickets: TicketSimple[];
|
||||
}>();
|
||||
|
||||
defineEmits<{
|
||||
"view-ticket": [ticketId: number];
|
||||
"edit-ticket": [ticketId: number];
|
||||
}>();
|
||||
</script>
|
@ -0,0 +1,128 @@
|
||||
<template>
|
||||
<div class="card rounded-4 mb-3">
|
||||
<div class="card-body">
|
||||
<div class="wrap-header">
|
||||
<div class="wh-row d-flex justify-content-between align-items-center">
|
||||
<div class="wh-col">
|
||||
<span
|
||||
v-if="ticket.currentMotive"
|
||||
class="h2"
|
||||
style="color: var(--bs-chill-blue); font-variant: all-small-caps"
|
||||
>
|
||||
{{ getTicketTitle(ticket) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="wh-col">
|
||||
<emergency-component
|
||||
:new_emergency="ticket.emergency"
|
||||
v-if="ticket.emergency"
|
||||
/>
|
||||
</div>
|
||||
<div class="wh-col">
|
||||
<state-component
|
||||
v-if="ticket.currentState"
|
||||
:new_state="ticket.currentState"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wh-row">
|
||||
<div class="wh-col">#{{ ticket.id }}</div>
|
||||
<div class="wh-col" v-if="ticket.createdAt">
|
||||
<span
|
||||
v-if="ticket"
|
||||
:title="formatDateTime(ticket.createdAt.datetime, 'long', 'long')"
|
||||
style="font-style: italic"
|
||||
>
|
||||
{{ getSinceCreated(ticket.createdAt.datetime, new Date()) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body pt-0">
|
||||
<div class="wrap-list">
|
||||
<div class="wl-row">
|
||||
<div class="wl-col title text-end">
|
||||
<h3>Attribué à</h3>
|
||||
</div>
|
||||
<div class="wl-col list">
|
||||
<addressee-component :addressees="ticket.currentAddressees" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="wl-row">
|
||||
<div class="wl-col title text-end">
|
||||
<h3>Patients concernés</h3>
|
||||
</div>
|
||||
<div class="wl-col list">
|
||||
<person-component :entities="ticket.currentPersons" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="wl-row">
|
||||
<div class="wl-col title text-end">
|
||||
<h3>Appelants</h3>
|
||||
</div>
|
||||
<div class="wl-col list">
|
||||
<person-component
|
||||
:entities="
|
||||
ticket.caller
|
||||
? ([ticket.caller] as Person[] | Thirdparty[])
|
||||
: []
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="my-0" />
|
||||
<div class="card-footer bg-transparent border-0">
|
||||
<ul class="record_actions mb-0">
|
||||
<li>
|
||||
<button
|
||||
class="btn btn-view btn-outline-secondary"
|
||||
type="button"
|
||||
@click="$emit('view-ticket', ticket.id)"
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
class="btn btn-update btn-outline-primary"
|
||||
type="button"
|
||||
@click="emit('edit-ticket', ticket.id)"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { defineProps, defineEmits } from "vue";
|
||||
|
||||
// Types
|
||||
import { TicketSimple } from "../../../types";
|
||||
import { Person } from "ChillPersonAssets/types";
|
||||
import { Thirdparty } from "src/Bundle/ChillThirdPartyBundle/Resources/public/types";
|
||||
|
||||
// Utils
|
||||
import {
|
||||
getSinceCreated,
|
||||
formatDateTime,
|
||||
getTicketTitle,
|
||||
} from "../utils/utils";
|
||||
|
||||
// Components
|
||||
import EmergencyComponent from "./Emergency/EmergencyComponent.vue";
|
||||
import StateComponent from "./State/StateComponent.vue";
|
||||
import AddresseeComponent from "./Addressee/AddresseeComponent.vue";
|
||||
import PersonComponent from "./Person/PersonComponent.vue";
|
||||
|
||||
defineProps<{
|
||||
ticket: TicketSimple;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
"edit-ticket": [ticketId: number];
|
||||
"view-ticket": [ticketId: number];
|
||||
}>();
|
||||
</script>
|
@ -1,36 +0,0 @@
|
||||
<template>
|
||||
<div class="d-flex justify-content-end">
|
||||
<div class="btn-group" @click="handleClick">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-light dropdown-toggle"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
>
|
||||
{{ trans(CHILL_TICKET_TICKET_PREVIOUS_TICKETS) }}
|
||||
<span
|
||||
class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-chill-green"
|
||||
>
|
||||
{{ tickets.length }}
|
||||
<span class="visually-hidden">Tickets</span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// Translations
|
||||
import { trans, CHILL_TICKET_TICKET_PREVIOUS_TICKETS } from "translator";
|
||||
|
||||
// Types
|
||||
import { Ticket } from "../../../types";
|
||||
|
||||
defineProps<{ tickets: Ticket[] }>();
|
||||
|
||||
function handleClick() {
|
||||
alert("Sera disponible plus tard");
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
@ -1,18 +1,23 @@
|
||||
import { Module } from "vuex";
|
||||
import { RootState } from "..";
|
||||
|
||||
import { Ticket, TicketEmergencyState } from "../../../../types";
|
||||
import { Ticket, TicketEmergencyState, TicketSimple } from "../../../../types";
|
||||
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods";
|
||||
import { ApiException } from "../../../../../../../../ChillMainBundle/Resources/public/types";
|
||||
import { getSinceCreated } from "../../utils/utils";
|
||||
|
||||
export interface State {
|
||||
ticket: Ticket;
|
||||
previous_tickets: TicketSimple[];
|
||||
previous_ticket_details: Ticket;
|
||||
action_icons: object;
|
||||
}
|
||||
|
||||
export const moduleTicket: Module<State, RootState> = {
|
||||
state: () => ({
|
||||
ticket: {} as Ticket,
|
||||
previous_tickets: [] as TicketSimple[],
|
||||
previous_ticket_details: {} as Ticket,
|
||||
action_icons: {
|
||||
add_person: "fa fa-user-plus",
|
||||
add_comment: "fa fa-comment",
|
||||
@ -37,18 +42,42 @@ export const moduleTicket: Module<State, RootState> = {
|
||||
);
|
||||
return state.ticket;
|
||||
},
|
||||
|
||||
getTicketHistory(state) {
|
||||
return state.ticket.history;
|
||||
},
|
||||
getPreviousTickets(state) {
|
||||
return state.previous_tickets;
|
||||
},
|
||||
getPreviousTicketHistory(state) {
|
||||
return state.previous_ticket_details.history;
|
||||
},
|
||||
getPreviousTicketDetails(state) {
|
||||
return state.previous_ticket_details;
|
||||
},
|
||||
getCurrentPersons(state) {
|
||||
return state.ticket.currentPersons ?? [];
|
||||
},
|
||||
getActionIcons(state) {
|
||||
return state.action_icons;
|
||||
},
|
||||
getDistinctAddressesHistory(state) {
|
||||
return state.ticket.history;
|
||||
getSinceCreated: (state) => (currentTime: Date) => {
|
||||
if (!state.ticket.createdAt) {
|
||||
return "";
|
||||
}
|
||||
return getSinceCreated(state.ticket.createdAt.datetime, currentTime);
|
||||
},
|
||||
},
|
||||
|
||||
mutations: {
|
||||
setTicket(state, ticket: Ticket) {
|
||||
state.ticket = ticket;
|
||||
},
|
||||
setPreviousTickets(state, previousTickets: TicketSimple[]) {
|
||||
state.previous_tickets = previousTickets;
|
||||
},
|
||||
setPreviousTicketDetails(state, ticket: Ticket) {
|
||||
state.previous_ticket_details = ticket;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
async closeTicket({ commit, state }) {
|
||||
@ -87,5 +116,39 @@ export const moduleTicket: Module<State, RootState> = {
|
||||
throw error.name;
|
||||
}
|
||||
},
|
||||
|
||||
async fetchTicketsByPerson({ commit, state }) {
|
||||
try {
|
||||
if (state.ticket.currentPersons.length === 0) {
|
||||
commit("setPreviousTickets", []);
|
||||
return;
|
||||
}
|
||||
const { results }: { results: TicketSimple[] } = await makeFetch(
|
||||
"GET",
|
||||
`/api/1.0/ticket/ticket/list?byPerson=${state.ticket.currentPersons.map((person) => person.id).join(",")}`,
|
||||
);
|
||||
const excludeCurrentTicket = results.filter(
|
||||
(ticket) => ticket.id !== state.ticket.id,
|
||||
);
|
||||
commit("setPreviousTickets", excludeCurrentTicket);
|
||||
} catch (e: unknown) {
|
||||
const error = e as ApiException;
|
||||
throw error.name;
|
||||
}
|
||||
},
|
||||
|
||||
async fetchPreviousTicketDetails({ commit }, ticketId: number) {
|
||||
try {
|
||||
const ticket: Ticket = await makeFetch(
|
||||
"GET",
|
||||
`/api/1.0/ticket/ticket/${ticketId}`,
|
||||
);
|
||||
commit("setPreviousTicketDetails", ticket);
|
||||
return ticket;
|
||||
} catch (e: unknown) {
|
||||
const error = e as ApiException;
|
||||
throw error.name;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -0,0 +1,94 @@
|
||||
import { ISOToDatetime } from "../../../../../../../../Bundle/ChillMainBundle/Resources/public/chill/js/date";
|
||||
import {
|
||||
trans,
|
||||
CHILL_TICKET_TICKET_BANNER_DAYS,
|
||||
CHILL_TICKET_TICKET_BANNER_HOURS,
|
||||
CHILL_TICKET_TICKET_BANNER_MINUTES,
|
||||
CHILL_TICKET_TICKET_BANNER_SECONDS,
|
||||
CHILL_TICKET_TICKET_BANNER_AND,
|
||||
CHILL_TICKET_TICKET_BANNER_NO_MOTIVE,
|
||||
} from "translator";
|
||||
import { Ticket, TicketSimple } from "../../../types";
|
||||
|
||||
/**
|
||||
* Calcule et formate le temps écoulé depuis une date de création
|
||||
* @param createdAt La date de création au format ISO
|
||||
* @param currentTime La date actuelle
|
||||
* @returns Une chaîne formatée représentant le temps écoulé
|
||||
*/
|
||||
export function getSinceCreated(createdAt: string, currentTime: Date): string {
|
||||
const date = ISOToDatetime(createdAt);
|
||||
if (date == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const timeDiff = Math.abs(currentTime.getTime() - date.getTime());
|
||||
const daysDiff = Math.floor(timeDiff / (1000 * 3600 * 24));
|
||||
const hoursDiff = Math.floor((timeDiff % (1000 * 3600 * 24)) / (1000 * 3600));
|
||||
const minutesDiff = Math.floor((timeDiff % (1000 * 3600)) / (1000 * 60));
|
||||
const secondsDiff = Math.floor((timeDiff % (1000 * 60)) / 1000);
|
||||
|
||||
// On construit la liste des parties à afficher
|
||||
const parts: string[] = [];
|
||||
if (daysDiff > 0) {
|
||||
parts.push(trans(CHILL_TICKET_TICKET_BANNER_DAYS, { count: daysDiff }));
|
||||
}
|
||||
if (hoursDiff > 0 || daysDiff > 0) {
|
||||
parts.push(
|
||||
trans(CHILL_TICKET_TICKET_BANNER_HOURS, {
|
||||
count: hoursDiff,
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (minutesDiff > 0 || hoursDiff > 0 || daysDiff > 0) {
|
||||
parts.push(
|
||||
trans(CHILL_TICKET_TICKET_BANNER_MINUTES, {
|
||||
count: minutesDiff,
|
||||
}),
|
||||
);
|
||||
}
|
||||
if (parts.length === 0) {
|
||||
return trans(CHILL_TICKET_TICKET_BANNER_SECONDS, {
|
||||
count: secondsDiff,
|
||||
});
|
||||
}
|
||||
if (parts.length > 1) {
|
||||
const last = parts.pop();
|
||||
return (
|
||||
parts.join(", ") +
|
||||
" " +
|
||||
trans(CHILL_TICKET_TICKET_BANNER_AND) +
|
||||
" " +
|
||||
last
|
||||
);
|
||||
}
|
||||
return parts[0];
|
||||
}
|
||||
|
||||
export function localizeTranslatableString(
|
||||
translatableString: Record<string, string> | string,
|
||||
): string {
|
||||
// This would be implemented based on your localization logic
|
||||
if (typeof translatableString === "string") {
|
||||
return translatableString;
|
||||
}
|
||||
// Assuming it's an object with locale keys
|
||||
return translatableString?.fr || translatableString?.en || "Unknown";
|
||||
}
|
||||
|
||||
export function formatDateTime(
|
||||
dateTime: string,
|
||||
dateStyle: string,
|
||||
timeStyle: string,
|
||||
): string {
|
||||
return new Date(dateTime).toLocaleString("fr-FR", {
|
||||
dateStyle: dateStyle as "short" | "medium" | "long" | "full",
|
||||
timeStyle: timeStyle as "short" | "medium" | "long" | "full",
|
||||
});
|
||||
}
|
||||
export function getTicketTitle(ticket: Ticket | TicketSimple): string {
|
||||
if (ticket.currentMotive) {
|
||||
return `#${ticket.id} ${localizeTranslatableString(ticket.currentMotive.label)}`;
|
||||
}
|
||||
return `#${ticket.id} ${trans(CHILL_TICKET_TICKET_BANNER_NO_MOTIVE)}`;
|
||||
}
|
@ -45,6 +45,7 @@ final class TicketNormalizer implements NormalizerInterface, NormalizerAwareInte
|
||||
'type' => 'ticket_ticket',
|
||||
'id' => $object->getId(),
|
||||
'externalRef' => $object->getExternalRef(),
|
||||
'createdAt' => $this->normalizer->normalize($object->getCreatedAt(), $format, $context),
|
||||
'currentPersons' => $this->normalizer->normalize($object->getPersons(), $format, [
|
||||
'groups' => 'read',
|
||||
]),
|
||||
@ -65,7 +66,6 @@ final class TicketNormalizer implements NormalizerInterface, NormalizerAwareInte
|
||||
$data += [
|
||||
'type_extended' => 'ticket_ticket:extended',
|
||||
'history' => array_values($this->serializeHistory($object, $format, ['groups' => 'read'])),
|
||||
'createdAt' => $this->normalizer->normalize($object->getCreatedAt(), $format, $context),
|
||||
'updatedAt' => $this->normalizer->normalize($object->getUpdatedAt(), $format, $context),
|
||||
'updatedBy' => $this->normalizer->normalize($object->getUpdatedBy(), $format, $context),
|
||||
'createdBy' => $this->normalizer->normalize($object->getCreatedBy(), $format, $context),
|
||||
|
@ -1,6 +1,8 @@
|
||||
chill_ticket:
|
||||
list:
|
||||
title: Tickets
|
||||
title_previous_tickets: "{name, select, other {Précédent ticket de {name}} undefined {Précédent ticket}}"
|
||||
no_tickets: "Aucun ticket"
|
||||
filter:
|
||||
to_me: Tickets qui me sont attribués
|
||||
in_alert: Tickets en alerte (délai de résolution dépassé)
|
||||
@ -51,9 +53,9 @@ chill_ticket:
|
||||
success: "Appelants et usagers mis à jour"
|
||||
error: "Aucun usager sélectionné"
|
||||
banner:
|
||||
concerned_usager: "Usagers concernés"
|
||||
speaker: "Attribué à"
|
||||
caller: "Appelant"
|
||||
person: "{count, plural, =0 {Aucun usager concerné} =1 {Usager concerné} other {Usagers concernés}}"
|
||||
speaker: "{count, plural, =0 {Aucun intervenant} =1 {Attribué à} other {Attribués à}}"
|
||||
caller: "{count, plural, =0 {Aucun appelant} =1 {Appelant} other {Appelants}}"
|
||||
open: "Ouvert"
|
||||
closed: "Fermé"
|
||||
since: "Depuis {time}"
|
||||
|
@ -282,12 +282,12 @@ class TicketNormalizerTest extends KernelTestCase
|
||||
'currentState',
|
||||
'emergency',
|
||||
'caller',
|
||||
'createdAt',
|
||||
];
|
||||
|
||||
// Keys that should not be present in read:simple normalization
|
||||
$unexpectedKeys = [
|
||||
'history',
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
'updatedBy',
|
||||
'createdBy',
|
||||
|
Loading…
x
Reference in New Issue
Block a user