mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-14 14:24:24 +00:00
Merge branch 'ticket-app-create-template' into 'ticket-app-master'
Mise à jour des messages de l'interface utilisateur pour inclure les... See merge request Chill-Projet/chill-bundles!689
This commit is contained in:
commit
76c076a5f3
6
.changes/unreleased/Fixed-20240416-161817.yaml
Normal file
6
.changes/unreleased/Fixed-20240416-161817.yaml
Normal file
@ -0,0 +1,6 @@
|
||||
kind: Fixed
|
||||
body: Fix broken link in homepage when a evaluation from a closed acc period was present
|
||||
in the homepage widget
|
||||
time: 2024-04-16T16:18:17.888645172+02:00
|
||||
custom:
|
||||
Issue: "270"
|
3
.changes/v2.18.2.md
Normal file
3
.changes/v2.18.2.md
Normal file
@ -0,0 +1,3 @@
|
||||
## v2.18.2 - 2024-04-12
|
||||
### Fixed
|
||||
* ([#250](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/250)) Postal codes import : fix the source URL and the keys to handle each record
|
@ -6,6 +6,10 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html),
|
||||
and is generated by [Changie](https://github.com/miniscruff/changie).
|
||||
|
||||
|
||||
## v2.18.2 - 2024-04-12
|
||||
### Fixed
|
||||
* ([#250](https://gitlab.com/Chill-Projet/chill-bundles/-/issues/250)) Postal codes import : fix the source URL and the keys to handle each record
|
||||
|
||||
## v2.18.1 - 2024-03-26
|
||||
### Fixed
|
||||
* Fix layout issue in document generation for admin (minor)
|
||||
|
@ -8,6 +8,16 @@ Chill can store a list of geolocated address references, which are used to sugge
|
||||
|
||||
Those addresses may be load from a dedicated source.
|
||||
|
||||
Countries
|
||||
=========
|
||||
|
||||
In order to load addresses into the chill application we first have to make sure that a list of countries is present.
|
||||
To import the countries run the following command.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
bin/console chill:main:countries:populate
|
||||
|
||||
In France
|
||||
=========
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<span class="chill-entity entity-user">
|
||||
{{ user.label }}
|
||||
{{ user }}
|
||||
<span class="user-job" v-if="user.user_job !== null">({{ user.user_job.label.fr }})</span> <span class="main-scope" v-if="user.main_scope !== null">({{ user.main_scope.name.fr }})</span> <span v-if="user.isAbsent" class="badge bg-danger rounded-pill" :title="Absent">A</span>
|
||||
</span>
|
||||
</template>
|
||||
|
@ -23,7 +23,7 @@ use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
*/
|
||||
class PostalCodeFRFromOpenData
|
||||
{
|
||||
private const CSV = 'https://datanova.laposte.fr/data-fair/api/v1/datasets/laposte-hexasmal/data-files/019HexaSmal.csv';
|
||||
private const CSV = 'https://datanova.laposte.fr/data-fair/api/v1/datasets/laposte-hexasmal/metadata-attachments/base-officielle-codes-postaux.csv';
|
||||
|
||||
public function __construct(private readonly PostalCodeBaseImporter $baseImporter, private readonly HttpClientInterface $client, private readonly LoggerInterface $logger) {}
|
||||
|
||||
@ -48,7 +48,7 @@ class PostalCodeFRFromOpenData
|
||||
fseek($tmpfile, 0);
|
||||
|
||||
$csv = Reader::createFromStream($tmpfile);
|
||||
$csv->setDelimiter(';');
|
||||
$csv->setDelimiter(',');
|
||||
$csv->setHeaderOffset(0);
|
||||
|
||||
foreach ($csv as $offset => $record) {
|
||||
@ -63,23 +63,23 @@ class PostalCodeFRFromOpenData
|
||||
|
||||
private function handleRecord(array $record): void
|
||||
{
|
||||
if ('' !== trim($record['coordonnees_geographiques'] ?? $record['coordonnees_gps'])) {
|
||||
[$lat, $lon] = array_map(static fn ($el) => (float) trim($el), explode(',', $record['coordonnees_geographiques'] ?? $record['coordonnees_gps']));
|
||||
if ('' !== trim((string) $record['_geopoint'])) {
|
||||
[$lat, $lon] = array_map(static fn ($el) => (float) trim($el), explode(',', (string) $record['_geopoint']));
|
||||
} else {
|
||||
$lat = $lon = 0.0;
|
||||
}
|
||||
|
||||
$ref = trim((string) $record['Code_commune_INSEE']);
|
||||
$ref = trim((string) $record['code_commune_insee']);
|
||||
|
||||
if (str_starts_with($ref, '987')) {
|
||||
// some differences in French Polynesia
|
||||
$ref .= '.'.trim((string) $record['Libellé_d_acheminement']);
|
||||
$ref .= '.'.trim((string) $record['libelle_d_acheminement']);
|
||||
}
|
||||
|
||||
$this->baseImporter->importCode(
|
||||
'FR',
|
||||
trim((string) $record['Libellé_d_acheminement']),
|
||||
trim((string) $record['Code_postal']),
|
||||
trim((string) $record['libelle_d_acheminement']),
|
||||
trim((string) $record['code_postal']),
|
||||
$ref,
|
||||
'INSEE',
|
||||
$lat,
|
||||
|
@ -12,6 +12,7 @@ declare(strict_types=1);
|
||||
namespace Chill\PersonBundle\Repository\AccompanyingPeriod;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWork;
|
||||
use Chill\PersonBundle\Entity\AccompanyingPeriod\AccompanyingPeriodWorkEvaluation;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
@ -88,6 +89,7 @@ class AccompanyingPeriodWorkEvaluationRepository implements ObjectRepository
|
||||
->where(
|
||||
$qb->expr()->andX(
|
||||
$qb->expr()->isNull('e.endDate'),
|
||||
$qb->expr()->neq('period.step', ':closed'),
|
||||
$qb->expr()->gte(':now', $qb->expr()->diff('e.maxDate', 'e.warningInterval')),
|
||||
$qb->expr()->orX(
|
||||
$qb->expr()->eq('period.user', ':user'),
|
||||
@ -100,6 +102,7 @@ class AccompanyingPeriodWorkEvaluationRepository implements ObjectRepository
|
||||
->setParameters([
|
||||
'user' => $user,
|
||||
'now' => new \DateTimeImmutable('now'),
|
||||
'closed' => AccompanyingPeriod::STEP_CLOSED,
|
||||
]);
|
||||
|
||||
return $qb;
|
||||
|
@ -3,10 +3,10 @@
|
||||
<h2><a id="section-10"></a>{{ $t('persons_associated.title')}}</h2>
|
||||
|
||||
<div v-if="currentParticipations.length > 0">
|
||||
<label class="col-form-label">{{ $tc('persons_associated.counter', counter) }}</label>
|
||||
<label class="col-form-label">{{ $t('persons_associated.counter', { count: counter }) }}</label>
|
||||
</div>
|
||||
<div v-else>
|
||||
<label class="chill-no-data-statement">{{ $tc('persons_associated.counter', counter) }}</label>
|
||||
<label class="chill-no-data-statement">{{ $t('persons_associated.counter', { count: counter }) }}</label>
|
||||
</div>
|
||||
|
||||
<div v-if="participationWithoutHousehold.length > 0" class="alert alert-warning no-household">
|
||||
|
@ -4,10 +4,10 @@
|
||||
<h2><a id="section-90"></a>{{ $t('resources.title')}}</h2>
|
||||
|
||||
<div v-if="resources.length > 0">
|
||||
<label class="col-form-label">{{ $tc('resources.counter', counter) }}</label>
|
||||
<label class="col-form-label">{{ $t('resources.counter', { count: counter }) }}</label>
|
||||
</div>
|
||||
<div v-else>
|
||||
<label class="chill-no-data-statement">{{ $tc('resources.counter', counter) }}</label>
|
||||
<label class="chill-no-data-statement">{{ $t('resources.counter', { count: counter }) }}</label>
|
||||
</div>
|
||||
|
||||
<div class="flex-table mb-3">
|
||||
|
@ -23,7 +23,7 @@
|
||||
data-bs-toggle="collapse"
|
||||
aria-expanded="false"
|
||||
@click="toggleHouseholdSuggestion">
|
||||
{{ $tc('household_members_editor.show_household_suggestion', countHouseholdSuggestion) }}
|
||||
{{ $t('household_members_editor.show_household_suggestion', { count: countHouseholdSuggestion }) }}
|
||||
</button>
|
||||
<button v-if="showHouseholdSuggestion"
|
||||
class="accordion-button"
|
||||
|
@ -17,7 +17,7 @@
|
||||
<div class="search">
|
||||
|
||||
<label class="col-form-label" style="float: right;">
|
||||
{{ $tc('add_persons.suggested_counter', suggestedCounter) }}
|
||||
{{ $t('add_persons.suggested_counter', { count: suggestedCounter }) }}
|
||||
</label>
|
||||
|
||||
<input id="search-persons"
|
||||
@ -42,7 +42,7 @@
|
||||
</a>
|
||||
</span>
|
||||
<span v-if="selectedCounter > 0">
|
||||
{{ $tc('add_persons.selected_counter', selectedCounter) }}
|
||||
{{ $t('add_persons.selected_counter', { count: selectedCounter }) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -52,9 +52,7 @@
|
||||
{{ $t('renderbox.deathdate') + ' ' + deathdate }}
|
||||
</time>
|
||||
|
||||
<span v-if="options.addAge && person.birthdate" class="age">{{
|
||||
$tc('renderbox.years_old', person.age)
|
||||
}}</span>
|
||||
<span v-if="options.addAge && person.birthdate" class="age">{{ $t('renderbox.years_old', { n: person.age }) }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<span :class="'altname altname-' + altNameKey"> ({{ altNameLabel }})</span>
|
||||
</span>
|
||||
<span v-if="person.suffixText" class="suffixtext"> {{ person.suffixText }}</span>
|
||||
<span class="age" v-if="this.addAge && person.birthdate !== null && person.deathdate === null">{{ $tc('renderbox.years_old', person.age) }}</span>
|
||||
<span class="age" v-if="this.addAge && person.birthdate !== null && person.deathdate === null">{{ $t('renderbox.years_old', { n: person.age }) }}</span>
|
||||
<span v-else-if="this.addAge && person.deathdate !== null"> (‡)</span>
|
||||
</span>
|
||||
</template>
|
||||
|
@ -69,6 +69,13 @@ class Ticket implements TrackCreationInterface, TrackUpdateInterface
|
||||
#[ORM\OneToMany(targetEntity: PersonHistory::class, mappedBy: 'ticket')]
|
||||
private Collection $personHistories;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: User::class)]
|
||||
#[ORM\JoinColumn(nullable: true)]
|
||||
private ?User $updatedBy = null;
|
||||
|
||||
#[ORM\Column(type: \Doctrine\DBAL\Types\Types::DATETIME_IMMUTABLE, nullable: true)]
|
||||
private ?\DateTimeImmutable $createdAt = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->addresseeHistory = new ArrayCollection();
|
||||
@ -76,6 +83,7 @@ class Ticket implements TrackCreationInterface, TrackUpdateInterface
|
||||
$this->motiveHistories = new ArrayCollection();
|
||||
$this->personHistories = new ArrayCollection();
|
||||
$this->inputHistories = new ArrayCollection();
|
||||
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
@ -211,4 +219,16 @@ class Ticket implements TrackCreationInterface, TrackUpdateInterface
|
||||
{
|
||||
return $this->addresseeHistory;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): \DateTimeImmutable
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
|
||||
public function getUpdatedBy(): ?User
|
||||
{
|
||||
return $this->updatedBy;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -2,10 +2,9 @@ import {
|
||||
DateTime,
|
||||
TranslatableString,
|
||||
User,
|
||||
UserGroup,
|
||||
UserGroupOrUser
|
||||
} from "../../../../ChillMainBundle/Resources/public/types";
|
||||
import {Person} from "../../../../ChillPersonBundle/Resources/public/types";
|
||||
import { Person } from "../../../../ChillPersonBundle/Resources/public/types";
|
||||
|
||||
export interface Motive {
|
||||
type: "ticket_motive"
|
||||
@ -21,7 +20,7 @@ interface TicketHistory<T extends string, D extends object> {
|
||||
data: D
|
||||
}
|
||||
|
||||
interface PersonHistory {
|
||||
export interface PersonHistory {
|
||||
type: "ticket_person_history",
|
||||
id: number,
|
||||
startDate: DateTime,
|
||||
@ -32,7 +31,7 @@ interface PersonHistory {
|
||||
createdAt: DateTime|null
|
||||
}
|
||||
|
||||
interface MotiveHistory {
|
||||
export interface MotiveHistory {
|
||||
type: "ticket_motive_history",
|
||||
id: number,
|
||||
startDate: null,
|
||||
@ -42,7 +41,7 @@ interface MotiveHistory {
|
||||
createdAt: DateTime|null,
|
||||
}
|
||||
|
||||
interface Comment {
|
||||
export interface Comment {
|
||||
type: "ticket_comment",
|
||||
id: number,
|
||||
content: string,
|
||||
@ -52,7 +51,7 @@ interface Comment {
|
||||
updatedAt: DateTime|null,
|
||||
}
|
||||
|
||||
interface AddresseeHistory {
|
||||
export interface AddresseeHistory {
|
||||
type: "ticket_addressee_history",
|
||||
id: number,
|
||||
startDate: DateTime|null,
|
||||
@ -69,8 +68,9 @@ interface AddPersonEvent extends TicketHistory<"add_person", PersonHistory> {};
|
||||
interface AddCommentEvent extends TicketHistory<"add_comment", Comment> {};
|
||||
interface SetMotiveEvent extends TicketHistory<"set_motive", MotiveHistory> {};
|
||||
interface AddAddressee extends TicketHistory<"add_addressee", AddresseeHistory> {};
|
||||
interface RemoveAddressee extends TicketHistory<"remove_addressee", AddresseeHistory> {};
|
||||
|
||||
type TicketHistoryLine = AddPersonEvent | AddCommentEvent | SetMotiveEvent | AddAddressee;
|
||||
type TicketHistoryLine = AddPersonEvent | AddCommentEvent | SetMotiveEvent | AddAddressee | RemoveAddressee;
|
||||
|
||||
export interface Ticket {
|
||||
type: "ticket_ticket",
|
||||
@ -80,5 +80,7 @@ export interface Ticket {
|
||||
currentPersons: Person[],
|
||||
currentMotive: null|Motive,
|
||||
history: TicketHistoryLine[],
|
||||
createdAt: DateTime|null,
|
||||
updatedBy: User|null,
|
||||
}
|
||||
|
||||
|
@ -1,59 +1,161 @@
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<p>{{ ticket.externalRef }}</p>
|
||||
<p>{{ ticket.currentMotive }}</p>
|
||||
<Teleport to="#header-ticket-main">
|
||||
<div class="container-xxl text-primary">
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-sm-12 ps-md-5 ps-xxl-0">
|
||||
<h2>#{{ ticket.externalRef }}</h2>
|
||||
<h1 v-if="ticket.currentMotive">
|
||||
{{ ticket.currentMotive.label.fr }}
|
||||
</h1>
|
||||
<p class="chill-no-data-statement" v-else>
|
||||
{{ $t("banner.no_motive") }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-for="person in ticket.currentPersons as Person[]" :key="person.id">
|
||||
<p>{{ person.firstName }}</p>
|
||||
<p>{{ person.lastName }}</p>
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<div class="float-end">
|
||||
<h1>
|
||||
<span class="badge text-bg-chill-green text-white">
|
||||
{{ $t("banner.open") }}
|
||||
</span>
|
||||
</h1>
|
||||
<h3 class="fst-italic" v-if="ticket.createdAt">
|
||||
{{
|
||||
$t("banner.since", {
|
||||
count: getSince(ticket.createdAt),
|
||||
})
|
||||
}}
|
||||
</h3>
|
||||
</div>
|
||||
<div v-for="ticket_history_line in ticket.history">
|
||||
<p>{{ ticket_history_line.event_type}}</p>
|
||||
<p>{{ ticket_history_line.data}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
<Teleport to="#header-ticket-details">
|
||||
<div class="container-xxl">
|
||||
<div class="row justify-content-between">
|
||||
<!-- <div class="col-md-4 col-sm-12">
|
||||
<h3 class="text-primary">{{ $t("concerned_patient") }}</h3>
|
||||
</div> -->
|
||||
<div class="col-md-6 col-sm-12 ps-md-5 ps-xxl-0">
|
||||
<h3 class="text-primary">{{ $t("banner.caller") }}</h3>
|
||||
<h2>
|
||||
<person-render-box
|
||||
render="badge"
|
||||
v-for="person in ticket.currentPersons"
|
||||
:key="person.id"
|
||||
:person="person"
|
||||
:options="{
|
||||
addLink: true,
|
||||
addId: false,
|
||||
addAltNames: false,
|
||||
addEntity: true,
|
||||
addInfo: true,
|
||||
hLevel: 3,
|
||||
isMultiline: true,
|
||||
isConfidential: false,
|
||||
|
||||
}"
|
||||
/>
|
||||
</h2>
|
||||
</div>
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<h3 class="text-primary">{{ $t("banner.speaker") }}</h3>
|
||||
|
||||
<h2>
|
||||
<span
|
||||
class="badge text-bg-light m-1"
|
||||
v-for="user_group in ticket.currentAddressees.filter((addressee) => addressee.type == 'user') as Array<User>"
|
||||
:key="user_group.id"
|
||||
>
|
||||
{{ user_group.label }}
|
||||
</span>
|
||||
<span
|
||||
class="badge text-bg-light m-1"
|
||||
v-for="user_group in ticket.currentAddressees.filter((addressee) => addressee.type == 'user_group') as Array<UserGroup>"
|
||||
:key="user_group.id"
|
||||
>
|
||||
{{ user_group.label.fr }}
|
||||
</span>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
<div class="container-xxl pt-1" style="padding-bottom: 55px">
|
||||
<ticket-selector-component :tickets="[]" />
|
||||
<ticket-history-list-component :history="ticketHistory" />
|
||||
</div>
|
||||
<action-toolbar-component />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, inject, onMounted, ref } from "vue";
|
||||
import { useStore } from "vuex";
|
||||
|
||||
import { computed, defineComponent, inject, onMounted } from 'vue';
|
||||
import { useStore } from 'vuex';
|
||||
// Types
|
||||
import {
|
||||
DateTime,
|
||||
User,
|
||||
UserGroup,
|
||||
} from "../../../../../../ChillMainBundle/Resources/public/types";
|
||||
import { Motive, Ticket } from "../../types";
|
||||
|
||||
// Components
|
||||
import TicketSelectorComponent from "./components/TicketSelectorComponent.vue";
|
||||
import TicketHistoryListComponent from "./components/TicketHistoryListComponent.vue";
|
||||
import ActionToolbarComponent from "./components/ActionToolbarComponent.vue";
|
||||
import PersonRenderBox from "../../../../../../ChillPersonBundle/Resources/public/vuejs/_components/Entity/PersonRenderBox.vue";
|
||||
|
||||
// Types
|
||||
import { Person } from '../../../../../../ChillPersonBundle/Resources/public/types';
|
||||
import { Motive, Ticket } from '../../types';
|
||||
|
||||
|
||||
export default defineComponent({
|
||||
name: 'App',
|
||||
|
||||
export default defineComponent({
|
||||
name: "App",
|
||||
components: {
|
||||
TicketSelectorComponent,
|
||||
TicketHistoryListComponent,
|
||||
ActionToolbarComponent,
|
||||
PersonRenderBox,
|
||||
},
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const toast = inject('toast') as any;
|
||||
const toast = inject("toast") as any;
|
||||
const headline = ref({} as HTMLHeadingElement);
|
||||
|
||||
store.commit('setTicket', JSON.parse(window.initialTicket) as Ticket);
|
||||
store.commit("setTicket", JSON.parse(window.initialTicket) as Ticket);
|
||||
|
||||
const motives = computed(() => store.getters.getMotives as Motive[])
|
||||
const ticket = computed(() => store.getters.getTicket as Ticket)
|
||||
const motives = computed(() => store.getters.getMotives as Motive[]);
|
||||
const ticket = computed(() => store.getters.getTicket as Ticket);
|
||||
const ticketHistory = computed(
|
||||
() => store.getters.getDistinctAddressesHistory
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
function getSince(createdAt: any) {
|
||||
const today = new Date();
|
||||
const date = new Date(createdAt.date);
|
||||
|
||||
const timeDiff = Math.abs(today.getTime() - date.getTime());
|
||||
const daysDiff = Math.ceil(timeDiff / (1000 * 3600 * 24));
|
||||
return daysDiff;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
store.dispatch('fetchMotives')
|
||||
await store.dispatch("fetchMotives");
|
||||
await store.dispatch("fetchUserGroups");
|
||||
await store.dispatch("fetchUsers");
|
||||
} catch (error) {
|
||||
toast.error(error)
|
||||
};
|
||||
toast.error(error);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
ticketHistory,
|
||||
headline,
|
||||
motives,
|
||||
ticket,
|
||||
getSince,
|
||||
};
|
||||
},
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
<style lang="scss" scoped></style>
|
||||
|
@ -0,0 +1,191 @@
|
||||
<template>
|
||||
<div class="fixed-bottom">
|
||||
<div class="footer-ticket-details">
|
||||
<div class="tab-content p-2">
|
||||
|
||||
<div v-if="activeTab">
|
||||
<label class="col-form-label">
|
||||
{{ $t(`${activeTab}.title`) }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div v-if="activeTab === 'comment'">
|
||||
<form @submit.prevent="createComment">
|
||||
<ckeditor
|
||||
name="content"
|
||||
:placeholder="$t('comment.content')"
|
||||
:editor="editor"
|
||||
v-model="content"
|
||||
tag-name="textarea">
|
||||
</ckeditor>
|
||||
|
||||
<div class="d-flex justify-content-end p-2">
|
||||
<button class="btn btn-chill-green text-white float-right" type="submit">
|
||||
<i class="fa fa-pencil"></i>
|
||||
{{ $t("comment.save") }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div v-if="activeTab === 'transfert'">
|
||||
<form @submit.prevent="setAdressees" v-if="userGroups.length && users.length">
|
||||
<addressee-selector-component v-model="addressees" :user-groups="userGroups" :users="users" />
|
||||
<div class="d-flex justify-content-end p-1">
|
||||
<button class="btn btn-chill-green text-white float-right" type="submit">
|
||||
<i class="fa fa-pencil"></i>
|
||||
{{ $t("transfert.save") }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div v-if="activeTab === 'motive'">
|
||||
<form @submit.prevent="createMotive">
|
||||
|
||||
<motive-selector-component v-model="motive" :motives="motives" />
|
||||
<div class="d-flex justify-content-end p-1">
|
||||
<button class="btn btn-chill-green text-white float-right" type="submit">
|
||||
<i class="fa fa-pencil"></i>
|
||||
{{ $t("motive.save") }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-ticket-main">
|
||||
<ul class="nav nav-tabs justify-content-end">
|
||||
<li class="nav-item">
|
||||
<button type="button" class="m-2 btn btn-light" @click="activeTab = 'comment'">
|
||||
<i class="fa fa-plus"></i>
|
||||
{{ $t('comment.title') }}
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<button type="button" class="m-2 btn btn-light" @click="activeTab = 'transfert'">
|
||||
<i class="fa fa-paper-plane"></i>
|
||||
{{ $t('transfert.title') }}
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<button type="button" class="m-2 btn btn-light" @click="activeTab = 'motive'">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
{{ $t('motive.title') }}
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<button type="button" class="m-2 btn btn-light" @click="activeTab = ''">
|
||||
<i class="fa fa-bolt"></i>
|
||||
Fermer
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, inject, ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useStore } from "vuex";
|
||||
import CKEditor from '@ckeditor/ckeditor5-vue';
|
||||
import ClassicEditor from "../../../../../../../ChillMainBundle/Resources/public/module/ckeditor5";
|
||||
|
||||
// Types
|
||||
import { User, UserGroup, UserGroupOrUser } from "../../../../../../../ChillMainBundle/Resources/public/types";
|
||||
import { Comment, Motive, Ticket } from "../../../types";
|
||||
|
||||
// Component
|
||||
import MotiveSelectorComponent from "./MotiveSelectorComponent.vue";
|
||||
import AddresseeSelectorComponent from "./AddresseeSelectorComponent.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ActionToolbarComponent",
|
||||
components: {
|
||||
MotiveSelectorComponent,
|
||||
AddresseeSelectorComponent,
|
||||
ckeditor: CKEditor.component,
|
||||
},
|
||||
setup() {
|
||||
const store = useStore();
|
||||
const { t } = useI18n();
|
||||
const toast = inject('toast') as any;
|
||||
const activeTab = ref("");
|
||||
|
||||
const ticket = computed(() => store.getters.getTicket as Ticket);
|
||||
const motives = computed(() => store.getters.getMotives as Motive[]);
|
||||
const userGroups = computed(() => store.getters.getUserGroups as UserGroup[]);
|
||||
const users = computed(() => store.getters.getUsers as User[]);
|
||||
|
||||
const motive = ref(ticket.value.currentMotive ? ticket.value.currentMotive: {} as Motive);
|
||||
const content = ref('' as Comment["content"]);
|
||||
const addressees = ref([] as Array<UserGroupOrUser>);
|
||||
|
||||
|
||||
|
||||
async function createMotive() {
|
||||
try {
|
||||
await store.dispatch("createMotive", {
|
||||
ticketId: ticket.value.id,
|
||||
motive: motive.value,
|
||||
});
|
||||
toast.success(t("motive.success"))
|
||||
} catch (error) {
|
||||
toast.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
async function createComment() {
|
||||
try {
|
||||
await store.dispatch("createComment", {
|
||||
ticketId: ticket.value.id,
|
||||
content: content.value,
|
||||
});
|
||||
content.value = "";
|
||||
toast.success(t("comment.success"))
|
||||
} catch (error) {
|
||||
toast.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
async function setAdressees() {
|
||||
try {
|
||||
await store.dispatch("setAdressees", {
|
||||
ticketId: ticket.value.id,
|
||||
addressees: addressees.value,
|
||||
});
|
||||
toast.success(t("transfert.success"))
|
||||
} catch (error) {
|
||||
toast.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
activeTab,
|
||||
ticket,
|
||||
motives,
|
||||
motive,
|
||||
userGroups,
|
||||
addressees,
|
||||
users,
|
||||
content,
|
||||
editor: ClassicEditor,
|
||||
createMotive,
|
||||
createComment,
|
||||
setAdressees,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
div.fixed-bottom {
|
||||
div.footer-ticket-main {
|
||||
background: none repeat scroll 0 0 #cabb9f;
|
||||
}
|
||||
|
||||
div.footer-ticket-details {
|
||||
background: none repeat scroll 0 0 #efe2ca;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -0,0 +1,215 @@
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="col-12 col-lg-6 col-md-6 mb-2 text-center">
|
||||
<span class="m-1">
|
||||
<input
|
||||
type="radio"
|
||||
class="btn-check"
|
||||
name="options-outlined"
|
||||
id="level-none"
|
||||
autocomplete="off"
|
||||
:value="{}"
|
||||
v-model="userGroupLevel"
|
||||
/>
|
||||
<label :class="`btn btn-outline-primary`" for="level-none">
|
||||
Aucun
|
||||
</label>
|
||||
</span>
|
||||
<span
|
||||
v-for="userGroupItem in userGroups.filter(
|
||||
(userGroup) => userGroup.excludeKey == 'level'
|
||||
)"
|
||||
:key="userGroupItem.id"
|
||||
class="m-1"
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
class="btn-check"
|
||||
name="options-outlined"
|
||||
:id="`level-${userGroupItem.id}`"
|
||||
autocomplete="off"
|
||||
:value="userGroupItem"
|
||||
v-model="userGroupLevel"
|
||||
/>
|
||||
<label
|
||||
:class="`btn btn-${userGroupItem.id}`"
|
||||
:for="`level-${userGroupItem.id}`"
|
||||
:style="getUserGroupBtnColor(userGroupItem)"
|
||||
>
|
||||
{{ userGroupItem.label.fr }}
|
||||
</label>
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-12 col-lg-6 col-md-6 mb-2 text-center">
|
||||
<span
|
||||
v-for="userGroupItem in userGroups.filter(
|
||||
(userGroup) => userGroup.excludeKey == ''
|
||||
)"
|
||||
:key="userGroupItem.id"
|
||||
class="m-1"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="btn-check"
|
||||
name="options-outlined"
|
||||
:id="`user-group-${userGroupItem.id}`"
|
||||
autocomplete="off"
|
||||
:value="userGroupItem"
|
||||
v-model="userGroup"
|
||||
/>
|
||||
<label
|
||||
:class="`btn btn-${userGroupItem.id}`"
|
||||
:for="`user-group-${userGroupItem.id}`"
|
||||
:style="getUserGroupBtnColor(userGroupItem)"
|
||||
>
|
||||
{{ userGroupItem.label.fr }}
|
||||
</label>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-lg-6 col-md-6 mb-2 text-center">
|
||||
<add-persons
|
||||
:options="addPersonsOptions"
|
||||
key="add-person-ticket"
|
||||
buttonTitle="transfert.user_label"
|
||||
modalTitle="transfert.user_label"
|
||||
ref="addPersons"
|
||||
@addNewPersons="addNewEntity"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-12 col-lg-6 col-md-6 mb-2 mb-2 text-center">
|
||||
<span class="badge text-bg-light m-1" v-for="user in users">
|
||||
{{ user.username }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, computed, defineComponent, ref, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
|
||||
// Types
|
||||
import {
|
||||
User,
|
||||
UserGroup,
|
||||
UserGroupOrUser,
|
||||
} from "../../../../../../../ChillMainBundle/Resources/public/types";
|
||||
|
||||
// Components
|
||||
import AddPersons from "../../../../../../../ChillPersonBundle/Resources/public/vuejs/_components/AddPersons.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "AddresseeSelectorComponent",
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Array as PropType<UserGroupOrUser[]>,
|
||||
default: [],
|
||||
required: false,
|
||||
},
|
||||
userGroups: {
|
||||
type: Array as PropType<UserGroup[]>,
|
||||
required: true,
|
||||
},
|
||||
users: {
|
||||
type: Array as PropType<User[]>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
AddPersons,
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
|
||||
setup(props, ctx) {
|
||||
// Cant use UserGroupOrUser[] because of TS2367
|
||||
// TS2367: This comparison appears to be unintentional because the types '"user" | "chill_main_user_group"' and '"user_group"' have no overlap.
|
||||
const addressees = ref(props.modelValue as any[]);
|
||||
const userGroupLevel = ref({} as UserGroupOrUser);
|
||||
const userGroup = ref([] as UserGroupOrUser[]);
|
||||
const users = ref([] as User[]);
|
||||
const addPersons = ref();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
function getUserGroupBtnColor(userGroup: UserGroup) {
|
||||
return [
|
||||
`.btn-check:checked + .btn-${userGroup.id} {
|
||||
color: ${userGroup.foregroundColor};
|
||||
background-color: ${userGroup.backgroundColor};
|
||||
}`,
|
||||
];
|
||||
}
|
||||
function addNewEntity(datas: any) {
|
||||
const { selected, modal } = datas;
|
||||
users.value = selected.map((selected: any) => selected.result);
|
||||
addressees.value = addressees.value.filter(
|
||||
(addressee) => addressee.type === "user_group"
|
||||
);
|
||||
addressees.value = [...addressees.value, ...users.value];
|
||||
ctx.emit("update:modelValue", addressees.value);
|
||||
addPersons.value.resetSearch();
|
||||
modal.showModal = false;
|
||||
}
|
||||
|
||||
const addPersonsOptions = computed(() => {
|
||||
return {
|
||||
uniq: false,
|
||||
type: ["user"],
|
||||
priority: null,
|
||||
button: {
|
||||
size: "btn-sm",
|
||||
class: "btn-submit",
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
watch(userGroupLevel, (userGroupLevelAdd, userGroupLevelRem) => {
|
||||
if (userGroupLevelRem) {
|
||||
addressees.value.splice(
|
||||
addressees.value.indexOf(userGroupLevelRem),
|
||||
1
|
||||
);
|
||||
}
|
||||
addressees.value.push(userGroupLevelAdd);
|
||||
ctx.emit("update:modelValue", addressees.value);
|
||||
});
|
||||
|
||||
watch(userGroup, (userGroupAdd) => {
|
||||
addressees.value = addressees.value.filter(
|
||||
(addressee) => addressee.excludeKey !== ""
|
||||
);
|
||||
addressees.value = [...addressees.value, ...userGroupAdd];
|
||||
ctx.emit("update:modelValue", addressees.value);
|
||||
});
|
||||
|
||||
return {
|
||||
addressees,
|
||||
userGroupLevel,
|
||||
userGroup,
|
||||
users,
|
||||
addPersons,
|
||||
addPersonsOptions,
|
||||
addNewEntity,
|
||||
getUserGroupBtnColor,
|
||||
customUserGroupLabel(selectedUserGroup: UserGroup) {
|
||||
return selectedUserGroup.label
|
||||
? selectedUserGroup.label.fr
|
||||
: t("transfert.user_group_label");
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.btn-check:checked + .btn,
|
||||
:not(.btn-check) + .btn:active,
|
||||
.btn:first-child:active,
|
||||
.btn.active,
|
||||
.btn.show {
|
||||
color: white;
|
||||
box-shadow: 0 0 0 0.2rem var(--bs-chill-green);
|
||||
outline: 0;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div class="row">
|
||||
<div class="col-12 col-lg-4 col-md-6">
|
||||
<vue-multiselect name="selectMotive" id="selectMotive" label="label" :custom-label="customLabel"
|
||||
track-by="id" open-direction="top" :multiple="false" :searchable="true"
|
||||
:placeholder="$t('motive.label')" :select-label="$t('multiselect.select_label')"
|
||||
:deselect-label="$t('multiselect.deselect_label')" :selected-label="$t('multiselect.selected_label')"
|
||||
:options="motives" v-model="motive" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent, ref, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import VueMultiselect from "vue-multiselect";
|
||||
|
||||
// Types
|
||||
import { Motive } from "../../../types";
|
||||
|
||||
export default defineComponent({
|
||||
name: "MotiveSelectorComponent",
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Object as PropType<Motive>,
|
||||
required: false,
|
||||
},
|
||||
motives: {
|
||||
type: Object as PropType<Motive[]>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
components: {
|
||||
VueMultiselect,
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
|
||||
setup(props, ctx) {
|
||||
const motive = ref(props.modelValue);
|
||||
const { t } = useI18n();
|
||||
|
||||
watch(motive, (motive) => {
|
||||
ctx.emit("update:modelValue", motive);
|
||||
});
|
||||
|
||||
return {
|
||||
motive,
|
||||
customLabel(motive: Motive) {
|
||||
return motive.label ? motive.label.fr : t('motive.label');
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
@ -0,0 +1,49 @@
|
||||
<template>
|
||||
<div class="col-12" >
|
||||
<i class="fa fa-paper-plane" v-if="event_type === 'add_addressee'"></i>
|
||||
<i class="fa fa-paper-plane-o" v-else></i>
|
||||
<span class="mx-1" v-if="addressee.type == 'user_group'">
|
||||
{{
|
||||
$t(`history.${event_type}_user_group`, {
|
||||
user_group: addressee.label.fr,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<span class="mx-1" v-else-if="addressee.type == 'user'">
|
||||
{{
|
||||
$t(`history.${event_type}_user`, {
|
||||
user: addressee.username,
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent, ref } from "vue";
|
||||
|
||||
// Types
|
||||
import { AddresseeHistory } from "../../../types";
|
||||
import { UserGroupOrUser } from "../../../../../../../ChillMainBundle/Resources/public/types";
|
||||
|
||||
export default defineComponent({
|
||||
name: "TicketHistoryAddresseeComponent",
|
||||
props: {
|
||||
addresseeHistory: {
|
||||
type: Object as PropType<AddresseeHistory>,
|
||||
required: true,
|
||||
},
|
||||
event_type: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup(props, ctx) {
|
||||
return {
|
||||
addressee: ref(props.addresseeHistory.addressee as UserGroupOrUser),
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<div class="col-12">
|
||||
<i class="fa fa-comment"></i>
|
||||
<span class="mx-1">
|
||||
{{ $t("history.comment") }}
|
||||
</span>
|
||||
<div class="mt-2">
|
||||
<div v-html="convertMarkdownToHtml(commentHistory.content)"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from "vue";
|
||||
import { marked } from "marked";
|
||||
import DOMPurify from "dompurify";
|
||||
|
||||
// Types
|
||||
import { Comment } from "../../../types";
|
||||
|
||||
export default defineComponent({
|
||||
name: "TicketHistoryCommentComponent",
|
||||
props: {
|
||||
commentHistory: {
|
||||
type: Object as PropType<Comment>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
|
||||
const preprocess = (markdown: string): string => {
|
||||
return markdown;
|
||||
};
|
||||
|
||||
const postprocess = (html: string): string => {
|
||||
DOMPurify.addHook("afterSanitizeAttributes", (node: any) => {
|
||||
if ("target" in node) {
|
||||
node.setAttribute("target", "_blank");
|
||||
node.setAttribute("rel", "noopener noreferrer");
|
||||
}
|
||||
if (
|
||||
!node.hasAttribute("target") &&
|
||||
(node.hasAttribute("xlink:href") ||
|
||||
node.hasAttribute("href"))
|
||||
) {
|
||||
node.setAttribute("xlink:show", "new");
|
||||
}
|
||||
});
|
||||
|
||||
return DOMPurify.sanitize(html);
|
||||
};
|
||||
|
||||
const convertMarkdownToHtml = (markdown: string): string => {
|
||||
marked.use({ hooks: { postprocess, preprocess } });
|
||||
const rawHtml = marked(markdown) as string;
|
||||
return rawHtml;
|
||||
};
|
||||
return {
|
||||
convertMarkdownToHtml,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<div
|
||||
class="card my-2 bg-light"
|
||||
v-for="history_line in history"
|
||||
:key="history.indexOf(history_line)"
|
||||
>
|
||||
<template v-if="!Array.isArray(history_line)">
|
||||
<div class="card-header">
|
||||
<span class="fw-bold fst-italic">
|
||||
{{ formatDate(history_line.at) }}
|
||||
</span>
|
||||
<span class="badge bg-white text-black mx-1">{{
|
||||
history_line.by.username
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="card-body row fst-italic">
|
||||
<ticket-history-person-component
|
||||
:personHistory="history_line.data"
|
||||
v-if="history_line.event_type == 'add_person'"
|
||||
/>
|
||||
<ticket-history-motive-component
|
||||
:motiveHistory="history_line.data"
|
||||
v-else-if="history_line.event_type == 'set_motive'"
|
||||
/>
|
||||
<ticket-history-comment-component
|
||||
:commentHistory="history_line.data"
|
||||
v-else-if="history_line.event_type == 'add_comment'"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="card-header">
|
||||
<span class="fw-bold fst-italic">
|
||||
{{ formatDate(history_line[0].at) }}
|
||||
</span>
|
||||
<span class="badge bg-white text-black mx-1">{{
|
||||
history_line[0].by.username
|
||||
}}</span>
|
||||
</div>
|
||||
<div
|
||||
class="card-body row fst-italic"
|
||||
v-for="addressee in history_line"
|
||||
:key="history_line.indexOf(addressee)"
|
||||
>
|
||||
<ticket-history-addressee-component :addresseeHistory="addressee.data" :event_type="addressee.event_type"/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from "vue";
|
||||
import { DateTime } from "../../../../../../../ChillMainBundle/Resources/public/types";
|
||||
|
||||
// Types
|
||||
import { Ticket } from "../../../types";
|
||||
|
||||
// Components
|
||||
import TicketHistoryPersonComponent from "./TicketHistoryPersonComponent.vue";
|
||||
import TicketHistoryMotiveComponent from "./TicketHistoryMotiveComponent.vue";
|
||||
import TicketHistoryCommentComponent from "./TicketHistoryCommentComponent.vue";
|
||||
import TicketHistoryAddresseeComponent from "./TicketHistoryAddresseeComponent.vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "TicketHistoryListComponent",
|
||||
components: {
|
||||
TicketHistoryPersonComponent,
|
||||
TicketHistoryMotiveComponent,
|
||||
TicketHistoryCommentComponent,
|
||||
TicketHistoryAddresseeComponent,
|
||||
},
|
||||
props: {
|
||||
history: {
|
||||
type: Array as PropType<Ticket["history"]>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
setup() {
|
||||
function formatDate(d: DateTime) {
|
||||
const date = new Date(d.datetime);
|
||||
const month = date.toLocaleString("default", { month: "long" });
|
||||
return `${date.getDate()} ${month} ${date.getFullYear()}, ${date.toLocaleTimeString()}`;
|
||||
}
|
||||
|
||||
return { formatDate };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<div class="col-12">
|
||||
<i class="fa fa-paint-brush"></i>
|
||||
<span class="mx-1">
|
||||
{{ $t('history.motive',{ motive: motiveHistory.motive.label.fr }) }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
import { PropType, defineComponent } from 'vue';
|
||||
|
||||
// Types
|
||||
import { MotiveHistory } from '../../../types';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TicketHistoryMotiveComponent',
|
||||
props: {
|
||||
motiveHistory: {
|
||||
type: Object as PropType<MotiveHistory>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
setup() {}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<div class="col-12" v-if="personHistory.createdBy">
|
||||
<i class="fa fa-eyedropper"></i>
|
||||
<span class="mx-1">
|
||||
{{ $t("history.user", { username: personHistory.createdBy.username }) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<i class="fa fa-bolt" style="min-width: 16px"></i>
|
||||
<span class="mx-1">
|
||||
{{ $t("history.person", { person: personHistory.person.text }) }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { PropType, defineComponent } from "vue";
|
||||
|
||||
// Type
|
||||
import { PersonHistory, Ticket } from "../../../types";
|
||||
|
||||
export default defineComponent({
|
||||
name: "TicketHistoryPersonComponent",
|
||||
props: {
|
||||
personHistory: {
|
||||
type: Object as PropType<PersonHistory>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
setup() {},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
@ -0,0 +1,41 @@
|
||||
<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">
|
||||
{{ $t('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 lang="ts">
|
||||
|
||||
import { PropType, defineComponent } from 'vue';
|
||||
|
||||
// Types
|
||||
import { Ticket } from '../../../types';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TicketSelectorComponent',
|
||||
props: {
|
||||
tickets: {
|
||||
type: Object as PropType<Ticket[]>,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
function handleClick() {
|
||||
alert('Sera disponible plus tard')
|
||||
}
|
||||
return { handleClick }
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
@ -1,5 +1,52 @@
|
||||
export const messages = {
|
||||
import { multiSelectMessages } from "../../../../../../../ChillMainBundle/Resources/public/vuejs/_js/i18n";
|
||||
import { personMessages } from "../../../../../../../ChillPersonBundle/Resources/public/vuejs/_js/i18n";
|
||||
|
||||
const messages = {
|
||||
fr: {
|
||||
hello: "Bonjour {name}"
|
||||
}
|
||||
ticket: {
|
||||
previous_tickets: "Précédents tickets",
|
||||
},
|
||||
history: {
|
||||
person: "Ouverture par appel téléphonique de {person}",
|
||||
user: "Prise en charge par {username}",
|
||||
motive: "Motif indiqué: {motive}",
|
||||
comment: "Commentaire",
|
||||
add_addressee_user_group: "Groupe {user_group} transferé",
|
||||
remove_addressee_user_group: "Groupe {user_group} retiré",
|
||||
add_addressee_user: " Utilisateur {user} Transferé",
|
||||
remove_addressee_user: "Utilisateur {user} retiré",
|
||||
},
|
||||
comment: {
|
||||
title: "Commentaire",
|
||||
label: "Ajouter un commentaire",
|
||||
save: "Enregistrer",
|
||||
succcess: "Commentaire enregistré",
|
||||
content: "Ajouter un commentaire",
|
||||
},
|
||||
motive: {
|
||||
title: "Motif",
|
||||
label: "Choisir un motif",
|
||||
save: "Enregistrer",
|
||||
success: "Motif enregistré",
|
||||
},
|
||||
transfert: {
|
||||
title: "Transfert",
|
||||
user_group_label: "Transferer vers un groupe",
|
||||
user_label: "Transferer vers un ou plusieurs utilisateurs",
|
||||
save: "Enregistrer",
|
||||
success: "Transfert effectué",
|
||||
},
|
||||
close: "Fermer",
|
||||
banner: {
|
||||
concerned_patient: "Patient concerné",
|
||||
caller: "Appelant",
|
||||
speaker: "Intervenant",
|
||||
open: "Ouvert",
|
||||
since: "Aucun jour | Depuis 1 jour | Depuis {count} jours",
|
||||
no_motive: "Pas de motif",
|
||||
},
|
||||
},
|
||||
};
|
||||
Object.assign(messages.fr, multiSelectMessages.fr);
|
||||
Object.assign(messages.fr, personMessages.fr);
|
||||
export default messages;
|
||||
|
@ -2,12 +2,12 @@ import App from './App.vue';
|
||||
import {createApp} from "vue";
|
||||
|
||||
import { _createI18n } from "../../../../../../ChillMainBundle/Resources/public/vuejs/_js/i18n";
|
||||
import {messages} from "./i18n/messages";
|
||||
|
||||
import VueToast from 'vue-toast-notification';
|
||||
import 'vue-toast-notification/dist/theme-sugar.css';
|
||||
|
||||
import { store } from "./store";
|
||||
import messages from './i18n/messages';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@ -15,7 +15,7 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
const i18n = _createI18n(messages);
|
||||
const i18n = _createI18n(messages, false);
|
||||
|
||||
const _app = createApp({
|
||||
template: '<app></app>',
|
||||
|
@ -1,15 +1,22 @@
|
||||
import { createStore } from "vuex";
|
||||
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 UserStates, moduleUser } from "./modules/user";
|
||||
|
||||
export type RootState = {
|
||||
motive: MotiveStates;
|
||||
ticket: TicketStates;
|
||||
comment: CommentStates;
|
||||
user: UserStates;
|
||||
};
|
||||
|
||||
export const store = createStore({
|
||||
modules: {
|
||||
motive:moduleMotive,
|
||||
ticket:moduleTicket,
|
||||
comment:moduleComment,
|
||||
user:moduleUser,
|
||||
|
||||
}
|
||||
});
|
||||
|
@ -0,0 +1,40 @@
|
||||
import {
|
||||
fetchResults,
|
||||
makeFetch,
|
||||
} from "../../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
|
||||
|
||||
import { Module } from "vuex";
|
||||
import { RootState } from "..";
|
||||
|
||||
import { Comment } from "../../../../types";
|
||||
|
||||
export interface State {
|
||||
comments: Array<Comment>;
|
||||
}
|
||||
|
||||
export const moduleComment: Module<State, RootState> = {
|
||||
state: () => ({
|
||||
comments: [] as Array<Comment>,
|
||||
}),
|
||||
getters: {},
|
||||
mutations: {},
|
||||
actions: {
|
||||
async createComment(
|
||||
{ commit },
|
||||
datas: { ticketId: number; content: Comment["content"] }
|
||||
) {
|
||||
const { ticketId, content } = datas;
|
||||
try {
|
||||
const result = await makeFetch(
|
||||
"POST",
|
||||
`/api/1.0/ticket/${ticketId}/comment/add`,
|
||||
{ content }
|
||||
);
|
||||
commit("setTicket", result);
|
||||
}
|
||||
catch(e: any) {
|
||||
throw e.name;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
@ -1,4 +1,7 @@
|
||||
import { fetchResults, makeFetch } from "../../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
|
||||
import {
|
||||
fetchResults,
|
||||
makeFetch,
|
||||
} from "../../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
|
||||
|
||||
import { Module } from "vuex";
|
||||
import { RootState } from "..";
|
||||
@ -9,31 +12,52 @@ export interface State {
|
||||
motives: Array<Motive>;
|
||||
}
|
||||
|
||||
export const moduleMotive: Module<State, RootState> ={
|
||||
export const moduleMotive: Module<State, RootState> = {
|
||||
state: () => ({
|
||||
motives: [] as Array<Motive>,
|
||||
}),
|
||||
getters: {
|
||||
getMotives(state) {
|
||||
return state.motives;
|
||||
}
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
setMotives(state, motives) {
|
||||
state.motives = motives;
|
||||
}
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
async fetchMotives({ commit }) {
|
||||
const results = await fetchResults("/api/1.0/ticket/motive.json") as Motive[];
|
||||
try {
|
||||
const results = (await fetchResults(
|
||||
"/api/1.0/ticket/motive.json"
|
||||
)) as Motive[];
|
||||
commit("setMotives", results);
|
||||
return results;
|
||||
},
|
||||
async createMotive({ commit }, datas: {currentMotiveId: number, motive: Motive}) {
|
||||
const { currentMotiveId, motive } = datas;
|
||||
const result = await makeFetch("POST", `/api/1.0/ticket/${currentMotiveId}/motive/set`, motive);
|
||||
commit("setMotives", result);
|
||||
return result;
|
||||
} catch (e: any) {
|
||||
throw e.name;
|
||||
}
|
||||
},
|
||||
|
||||
async createMotive(
|
||||
{ commit },
|
||||
datas: { ticketId: number; motive: Motive }
|
||||
) {
|
||||
const { ticketId, motive } = datas;
|
||||
try {
|
||||
const result = await makeFetch(
|
||||
"POST",
|
||||
`/api/1.0/ticket/${ticketId}/motive/set`,
|
||||
{
|
||||
motive: {
|
||||
id: motive.id,
|
||||
type: motive.type,
|
||||
},
|
||||
}
|
||||
);
|
||||
commit("setTicket", result);
|
||||
} catch (e: any) {
|
||||
throw e.name;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -1,5 +1,3 @@
|
||||
import { fetchResults, makeFetch } from "../../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
|
||||
|
||||
import { Module } from "vuex";
|
||||
import { RootState } from "..";
|
||||
|
||||
@ -16,7 +14,24 @@ export const moduleTicket: Module<State, RootState> ={
|
||||
getters: {
|
||||
getTicket(state) {
|
||||
return state.ticket;
|
||||
},
|
||||
getDistinctAddressesHistory(state) {
|
||||
const addresseeHistory = state.ticket.history.reduce((result, item) => {
|
||||
const { datetime } = item.at;
|
||||
if (!["add_addressee","remove_addressee"].includes(item.event_type)) {
|
||||
result[datetime] = item
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!result[datetime]) {
|
||||
result[datetime] = [];
|
||||
}
|
||||
result[datetime].push(item);
|
||||
return result;
|
||||
}, {} as any);
|
||||
return Object.values(addresseeHistory) as Array<Ticket["history"]>;
|
||||
}
|
||||
|
||||
},
|
||||
mutations: {
|
||||
setTicket(state, ticket) {
|
||||
|
@ -0,0 +1,84 @@
|
||||
import {
|
||||
fetchResults,
|
||||
makeFetch,
|
||||
} from "../../../../../../../../ChillMainBundle/Resources/public/lib/api/apiMethods";
|
||||
|
||||
import { Module } from "vuex";
|
||||
import { RootState } from "..";
|
||||
|
||||
import {
|
||||
User,
|
||||
UserGroup,
|
||||
UserGroupOrUser,
|
||||
} from "../../../../../../../../ChillMainBundle/Resources/public/types";
|
||||
|
||||
export interface State {
|
||||
userGroups: Array<UserGroup>;
|
||||
users: Array<User>;
|
||||
}
|
||||
|
||||
export const moduleUser: Module<State, RootState> = {
|
||||
state: () => ({
|
||||
userGroups: [] as Array<UserGroup>,
|
||||
users: [] as Array<User>,
|
||||
}),
|
||||
getters: {
|
||||
getUserGroups(state) {
|
||||
return state.userGroups;
|
||||
},
|
||||
getUsers(state) {
|
||||
return state.users;
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
setUserGroups(state, userGroups) {
|
||||
state.userGroups = userGroups;
|
||||
},
|
||||
setUsers(state, users) {
|
||||
state.users = users;
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
fetchUserGroups({ commit }) {
|
||||
try {
|
||||
fetchResults("/api/1.0/main/user-group.json").then(
|
||||
(results) => {
|
||||
commit("setUserGroups", results);
|
||||
}
|
||||
);
|
||||
} catch (e: any) {
|
||||
throw e.name;
|
||||
}
|
||||
},
|
||||
fetchUsers({ commit }) {
|
||||
try {
|
||||
fetchResults("/api/1.0/main/user.json").then((results) => {
|
||||
commit("setUsers", results);
|
||||
});
|
||||
} catch (e: any) {
|
||||
throw e.name;
|
||||
}
|
||||
},
|
||||
|
||||
async setAdressees(
|
||||
{ commit },
|
||||
datas: { ticketId: number; addressees: Array<UserGroupOrUser> }
|
||||
) {
|
||||
const { ticketId, addressees } = datas;
|
||||
try {
|
||||
const result = await makeFetch(
|
||||
"POST",
|
||||
`/api/1.0/ticket/${ticketId}/addressees/set`,
|
||||
{
|
||||
addressees: addressees.map((addressee) => {
|
||||
return { id: addressee.id, type: addressee.type };
|
||||
}),
|
||||
}
|
||||
);
|
||||
commit("setTicket", result);
|
||||
} catch (e: any) {
|
||||
throw e.name;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
@ -1,24 +1,8 @@
|
||||
<div class="banner banner-ticket">
|
||||
<div class="banner banner-ticket ">
|
||||
<div id="header-ticket-main" class="header-name">
|
||||
<div class="container-xxl">
|
||||
<div class="row">
|
||||
<div class="col-md-6 ps-md-5 ps-xxl-0">
|
||||
TODO
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
TODO
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="header-ticket-details" class="header-details">
|
||||
<div class="container-xxl">
|
||||
<div class="row justify-content-between">
|
||||
<div class="col-md-12 ps-md-5 ps-xxl-0 container">
|
||||
<p>TODO</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
@ -13,9 +13,5 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block wrapping_content %}
|
||||
<div class="row">
|
||||
<div class="col-md-8 col-sm-12">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -42,6 +42,8 @@ final class TicketNormalizer implements NormalizerInterface, NormalizerAwareInte
|
||||
'currentInputs' => $this->normalizer->normalize($object->getCurrentInputs(), $format, ['groups' => 'read']),
|
||||
'currentMotive' => $this->normalizer->normalize($object->getMotive(), $format, ['groups' => 'read']),
|
||||
'history' => array_values($this->serializeHistory($object, $format, ['groups' => 'read'])),
|
||||
'createdAt' => $object->getCreatedAt(),
|
||||
'updatedBy' => $object->getUpdatedBy(),
|
||||
];
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user