Merge branch '1677-create-ticket-list-for-user-file' into 'ticket-app-master'

Créer la page et la liste des tickets dans le dossier d'usager

See merge request Chill-Projet/chill-bundles!891
This commit is contained in:
2025-10-15 11:06:04 +00:00
17 changed files with 308 additions and 156 deletions

View File

@@ -11,38 +11,22 @@ declare(strict_types=1);
namespace Chill\TicketBundle\Controller;
use Chill\MainBundle\Templating\Listing\FilterOrderHelper;
use Chill\MainBundle\Templating\Listing\FilterOrderHelperFactory;
use Chill\MainBundle\Templating\TranslatableStringHelperInterface;
use Chill\TicketBundle\Entity\Motive;
use Chill\TicketBundle\Repository\MotiveRepository;
use Chill\TicketBundle\Repository\TicketRepositoryInterface;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Chill\PersonBundle\Entity\Person;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Security;
use Twig\Environment;
use Twig\Error\LoaderError;
use Twig\Error\RuntimeError;
use Twig\Error\SyntaxError;
final readonly class TicketListController
{
public function __construct(
private Security $security,
private TicketRepositoryInterface $ticketRepository,
private Environment $twig,
private FilterOrderHelperFactory $filterOrderHelperFactory,
// private MotiveRepository $motiveRepository,
// private TranslatableStringHelperInterface $translatableStringHelper,
) {}
/**
* @throws RuntimeError
* @throws SyntaxError
* @throws LoaderError
*/
#[Route('/{_locale}/ticket/ticket/list', name: 'chill_ticket_ticket_list')]
public function __invoke(Request $request): Response
{
@@ -50,35 +34,22 @@ final readonly class TicketListController
throw new AccessDeniedHttpException('only user can access this page');
}
$filter = $this->buildFilter();
$tickets = $this->ticketRepository->findAllOrdered();
return new Response(
$this->twig->render('@ChillTicket/Ticket/list.html.twig', [
'tickets' => $tickets,
'filter' => $filter,
])
$this->twig->render('@ChillTicket/Ticket/list.html.twig')
);
}
private function buildFilter(): FilterOrderHelper
#[Route('/{_locale}/ticket/by-person/{id}/list', name: 'chill_person_ticket_list')]
public function listByPerson(Request $request, Person $person): Response
{
// $motives = $this->motiveRepository->findAll();
if (!$this->security->isGranted(PersonVoter::SEE, $person)) {
throw new AccessDeniedHttpException('you are not allowed to see this person');
}
return $this->filterOrderHelperFactory
->create(self::class)
->addSingleCheckbox('to_me', 'chill_ticket.list.filter.to_me')
->addSingleCheckbox('in_alert', 'chill_ticket.list.filter.in_alert')
->addDateRange('created_between', 'chill_ticket.list.filter.created_between')
/*
->addEntityChoice('by_motive', 'chill_ticket.list.filter.by_motive', Motive::class, $motives, [
'choice_label' => fn (Motive $motive) => $this->translatableStringHelper->localize($motive->getLabel()),
'expanded' => true,
'multiple' => true,
'attr' => ['class' => 'select2'],
return new Response(
$this->twig->render('@ChillTicket/Person/list.html.twig', [
'person' => $person,
])
*/
->build();
);
}
}

View File

@@ -46,7 +46,7 @@ final class LoadMotives extends Fixture implements FixtureGroupInterface
continue;
}
$labels = explode(' > ', $data[0]);
$labels = explode(' > ', (string) $data[0]);
$parent = null;
while (count($labels) > 0) {

View File

@@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
/*
* Chill is a software for social workers
*
* For the full copyright and license information, please view
* the LICENSE file that was distributed with this source code.
*/
namespace Chill\TicketBundle\Menu;
use Chill\PersonBundle\Entity\Person;
use Chill\PersonBundle\Security\Authorization\PersonVoter;
use Chill\TicketBundle\Repository\TicketRepositoryInterface;
use Chill\MainBundle\Routing\LocalMenuBuilderInterface;
use Knp\Menu\MenuItem;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* Add menu entrie to person menu.
*
* Menu entries added :
*
* - person details ;
* - accompanying period (if `visible`)
*
* @implements LocalMenuBuilderInterface<array{person: Person}>
*/
class PersonMenuBuilder implements LocalMenuBuilderInterface
{
public function __construct(
private readonly AuthorizationCheckerInterface $authorizationChecker,
private readonly TranslatorInterface $translator,
private readonly TicketRepositoryInterface $ticketRepository,
) {}
/**
* @param array{person: Person} $parameters
*
* @return void
*/
public function buildMenu($menuId, MenuItem $menu, array $parameters)
{
/** @var Person $person */
$person = $parameters['person'];
if ($this->authorizationChecker->isGranted(PersonVoter::SEE, $person)) {
$menu->addChild($this->translator->trans('chill_ticket.list.title_menu'), [
'route' => 'chill_person_ticket_list',
'routeParameters' => [
'id' => $person->getId(),
],
])
->setExtras([
'order' => 150,
'counter' => 0 < ($nbTickets = $this->ticketRepository->countOpenedByPerson($person))
? $nbTickets : null,
]);
}
}
public static function getMenuIds(): array
{
return ['person'];
}
}

View File

@@ -11,6 +11,7 @@ declare(strict_types=1);
namespace Chill\TicketBundle\Repository;
use Chill\PersonBundle\Entity\Person;
use Chill\TicketBundle\Entity\Ticket;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Persistence\ObjectRepository;
@@ -61,4 +62,19 @@ final readonly class TicketRepository implements TicketRepositoryInterface
{
return $this->repository->findOneBy(['externalRef' => $extId]);
}
/**
* Count tickets associated with a person where endDate is null.
*/
public function countOpenedByPerson(Person $person): int
{
return (int) $this->objectManager->createQuery(
'SELECT COUNT(DISTINCT t.id) FROM '.$this->getClassName().' t
JOIN t.personHistories ph
WHERE ph.person = :person
AND ph.endDate IS NULL'
)
->setParameter('person', $person)
->getSingleScalarResult();
}
}

View File

@@ -12,6 +12,7 @@ declare(strict_types=1);
namespace Chill\TicketBundle\Repository;
use Chill\TicketBundle\Entity\Ticket;
use Chill\PersonBundle\Entity\Person;
use Doctrine\Persistence\ObjectRepository;
/**
@@ -25,4 +26,6 @@ interface TicketRepositoryInterface extends ObjectRepository
* @return list<Ticket>
*/
public function findAllOrdered(): array;
public function countOpenedByPerson(Person $person): int;
}

View File

@@ -177,8 +177,12 @@ export interface Ticket extends BaseTicket<"ticket_ticket:extended"> {
}
export interface TicketFilters {
byPerson: Person[];
byCreator: User[];
byAddressee: UserGroupOrUser[];
byCurrentState: TicketState[];
byCurrentStateEmergency: TicketEmergencyState[];
byMotives: Motive[];
byCreatedAfter: string;
byCreatedBefore: string;
byResponseTimeExceeded: boolean;
@@ -198,7 +202,7 @@ export interface TicketFilterParams {
byCreatedBefore?: string;
byResponseTimeExceeded?: string;
byAddresseeToMe?: boolean;
byTicketId?: number;
byTicketId?: number | null;
}
export interface TicketInitForm {

View File

@@ -1,16 +1,23 @@
<template>
<pick-entity
uniqid="ticket-addressee-selector"
:types="['user', 'user_group']"
:picked="selectedEntities"
:suggested="suggestedValues"
:multiple="true"
:removable-if-set="true"
:display-picked="true"
:label="label"
@add-new-entity="addNewEntity"
@remove-entity="removeEntity"
/>
<div
:class="{
'opacity-50': disabled,
}"
:style="disabled ? 'pointer-events: none;' : ''"
>
<pick-entity
uniqid="ticket-addressee-selector"
:types="['user', 'user_group']"
:picked="selectedEntities"
:suggested="suggestedValues"
:multiple="true"
:removable-if-set="true"
:display-picked="true"
:label="label"
@add-new-entity="addNewEntity"
@remove-entity="removeEntity"
/>
</div>
</template>
<script lang="ts" setup>
@@ -33,9 +40,11 @@ const props = withDefaults(
modelValue: Entities[];
suggested: Entities[];
label?: string;
disabled?: boolean;
}>(),
{
label: trans(CHILL_TICKET_TICKET_ADD_ADDRESSEE_USER_LABEL),
disabled: false,
},
);

View File

@@ -16,6 +16,7 @@
v-model="motive"
class="form-control"
@remove="(value: Motive) => $emit('remove', value)"
:disabled="disabled"
>
<template
#option="{
@@ -95,6 +96,10 @@ const props = defineProps({
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
});
const store = useStore();

View File

@@ -1,16 +1,23 @@
<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"
/>
<div
:class="{
'opacity-50': disabled,
}"
:style="disabled ? 'pointer-events: none;' : ''"
>
<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"
/>
</div>
</template>
<script setup lang="ts">
@@ -22,13 +29,19 @@ import PickEntity from "ChillMainAssets/vuejs/PickEntity/PickEntity.vue";
// Types
import { Entities, EntitiesOrMe, EntityType } from "ChillPersonAssets/types";
const props = defineProps<{
modelValue: EntitiesOrMe[] | EntitiesOrMe | null;
suggested: Entities[];
multiple: boolean;
types: EntityType[];
label: string;
}>();
const props = withDefaults(
defineProps<{
modelValue: EntitiesOrMe[] | EntitiesOrMe | null;
suggested: Entities[];
multiple: boolean;
types: EntityType[];
label: string;
disabled?: boolean;
}>(),
{
disabled: false,
},
);
const emit = defineEmits<{
"update:modelValue": [value: EntitiesOrMe[] | EntitiesOrMe | null];

View File

@@ -5,6 +5,7 @@
type="number"
class="form-control"
:placeholder="trans(CHILL_TICKET_LIST_FILTER_TICKET_ID)"
:disabled="disabled"
@input="
ticketId = isNaN(Number(($event.target as HTMLInputElement).value))
? null
@@ -26,9 +27,15 @@
import { ref, watch } from "vue";
// Translation
import { trans, CHILL_TICKET_LIST_FILTER_TICKET_ID } from "translator";
const props = defineProps<{
modelValue: number | null;
}>();
const props = withDefaults(
defineProps<{
modelValue: number | null;
disabled?: boolean;
}>(),
{
disabled: false,
},
);
const ticketId = ref<number | null>(props.modelValue);

View File

@@ -1,14 +1,12 @@
<template>
<div class="container-fluid">
<h1 class="text-primary">
{{ title }}
</h1>
<div class="row">
<div class="col-12 mb-4">
<ticket-filter-list-component
:resultCount="resultCount"
:available-persons="availablePersons"
:available-motives="availableMotives"
:ticket-filter-params="ticketFilterParams"
@filters-changed="handleFiltersChanged"
/>
</div>
@@ -35,7 +33,6 @@
<ticket-list-component
v-else
:tickets="ticketList"
:title="title"
:hasMoreTickets="pagination.next !== null"
@fetchNextPage="fetchNextPage"
/>
@@ -61,8 +58,10 @@ import { Pagination } from "ChillMainAssets/lib/api/apiMethods";
import { trans, CHILL_TICKET_LIST_LOADING_TICKET } from "translator";
const store = useStore();
const ticketFilterParams = window.ticketFilterParams
? window.ticketFilterParams
: null;
const title = window.title;
const isLoading = ref(false);
const ticketList = computed(
() => store.getters.getTicketList as TicketSimple[],
@@ -90,12 +89,27 @@ const fetchNextPage = async () => {
onMounted(async () => {
isLoading.value = true;
const filters: TicketFilterParams = {
byCurrentState: ["open"],
byCurrentStateEmergency: [],
byCreatedAfter: "",
byCreatedBefore: "",
byResponseTimeExceeded: "",
byAddresseeToMe: false,
byPerson: ticketFilterParams?.byPerson
? ticketFilterParams.byPerson.map((person) => person.id)
: [],
byCreator: ticketFilterParams?.byCreator
? ticketFilterParams.byCreator.map((creator) => creator.id)
: [],
byAddressee: ticketFilterParams?.byAddressee
? ticketFilterParams.byAddressee.map((addressee) => addressee.id)
: [],
byCurrentState: ticketFilterParams?.byCurrentState ?? ["open"],
byCurrentStateEmergency: ticketFilterParams?.byCurrentStateEmergency ?? [],
byMotives: ticketFilterParams?.byMotives
? ticketFilterParams.byMotives.map((motive) => motive.id)
: [],
byCreatedAfter: ticketFilterParams?.byCreatedAfter ?? "",
byCreatedBefore: ticketFilterParams?.byCreatedBefore ?? "",
byResponseTimeExceeded: ticketFilterParams?.byResponseTimeExceeded
? "true"
: "",
byAddresseeToMe: ticketFilterParams?.byAddresseeToMe ?? false,
byTicketId: ticketFilterParams?.byTicketId ?? null,
};
try {
await store.dispatch("fetchTicketList", filters);

View File

@@ -14,12 +14,13 @@
trans(CHILL_TICKET_LIST_FILTER_PERSONS_CONCERNED)
}}</label>
<persons-selector
v-model="selectedPersons"
v-model="filters.byPerson"
:suggested="availablePersons"
:multiple="true"
:types="['person']"
id="personSelector"
:label="trans(CHILL_TICKET_LIST_FILTER_BY_PERSON)"
:disabled="ticketFilterParams?.byPerson ? true : false"
/>
</div>
@@ -28,12 +29,13 @@
trans(CHILL_TICKET_LIST_FILTER_CREATORS)
}}</label>
<persons-selector
v-model="selectedCreator"
v-model="filters.byCreator"
:suggested="[]"
:multiple="true"
:types="['user']"
id="userSelector"
:label="trans(CHILL_TICKET_LIST_FILTER_BY_CREATOR)"
:disabled="ticketFilterParams?.byCreator ? true : false"
/>
</div>
@@ -42,10 +44,11 @@
trans(CHILL_TICKET_LIST_FILTER_ADDRESSEES)
}}</label>
<addressee-selector-component
v-model="selectedAddressees"
v-model="filters.byAddressee"
:suggested="[]"
:label="trans(CHILL_TICKET_LIST_FILTER_BY_ADDRESSEES)"
id="addresseeSelector"
:disabled="ticketFilterParams?.byAddressee ? true : false"
/>
</div>
<div class="col-md-6 mb-3">
@@ -60,12 +63,13 @@
:allow-parent-selection="true"
@remove="(motive) => removeMotive(motive)"
id="motiveSelector"
:disabled="ticketFilterParams?.byMotives ? true : false"
/>
<div class="mb-2" style="min-height: 2em">
<div class="d-flex flex-wrap gap-2">
<span
v-for="motive in selectedMotives"
v-for="motive in filters.byMotives"
:key="motive.id"
class="badge bg-secondary d-flex align-items-center gap-1"
>
@@ -75,6 +79,7 @@
class="btn-close btn-close-white"
:aria-label="trans(CHILL_TICKET_LIST_FILTER_REMOVE)"
@click="removeMotive(motive)"
:disabled="ticketFilterParams?.byMotives ? true : false"
></button>
</span>
</div>
@@ -96,6 +101,7 @@
}"
@update:model-value="handleStateToggle"
id="currentState"
:disabled="ticketFilterParams?.byCurrentState ? true : false"
/>
</div>
@@ -114,6 +120,9 @@
}"
@update:model-value="handleEmergencyToggle"
id="emergency"
:disabled="
ticketFilterParams?.byCurrentStateEmergency ? true : false
"
/>
</div>
</div>
@@ -129,6 +138,7 @@
class="form-check-input"
type="checkbox"
id="stateMe"
:disabled="ticketFilterParams?.byAddresseeToMe ? true : false"
/>
<label class="form-check-label" for="stateMe">
{{ trans(CHILL_TICKET_LIST_FILTER_TO_ME) }}
@@ -142,6 +152,9 @@
v-model="filters.byResponseTimeExceeded"
@change="handleResponseTimeExceededChange"
id="responseTimeExceeded"
:disabled="
ticketFilterParams?.byResponseTimeExceeded ? true : false
"
/>
<label class="form-check-label" for="responseTimeExceeded">
{{ trans(CHILL_TICKET_LIST_FILTER_RESPONSE_TIME_EXCEEDED) }}
@@ -157,7 +170,11 @@
<label class="form-label pe-2" for="ticketSelector">
{{ trans(CHILL_TICKET_LIST_FILTER_BY_TICKET_ID) }}
</label>
<ticket-selector v-model="filters.byTicketId" id="ticketSelector" />
<ticket-selector
v-model="filters.byTicketId"
id="ticketSelector"
:disabled="ticketFilterParams?.byTicketId ? true : false"
/>
</div>
</div>
@@ -170,7 +187,12 @@
default-value-time="00:00"
:model-value-date="filters.byCreatedAfter"
:model-value-time="byCreatedAfterTime"
:disabled="filters.byResponseTimeExceeded"
:disabled="
filters.byResponseTimeExceeded ||
ticketFilterParams?.byCreatedAfter
? true
: false
"
@update:modelValueDate="filters.byCreatedAfter = $event"
@update:modelValueTime="byCreatedAfterTime = $event"
/>
@@ -183,7 +205,12 @@
default-value-time="23:59"
:model-value-date="filters.byCreatedBefore"
:model-value-time="byCreatedBeforeTime"
:disabled="filters.byResponseTimeExceeded"
:disabled="
filters.byResponseTimeExceeded ||
ticketFilterParams?.byCreatedBefore
? true
: false
"
@update:modelValueDate="filters.byCreatedBefore = $event"
@update:modelValueTime="byCreatedBeforeTime = $event"
/>
@@ -227,7 +254,6 @@ import {
type TicketFilterParams,
type TicketFilters,
} from "../../../types";
import { User, UserGroupOrUser } from "ChillMainAssets/types";
// Translation
import {
@@ -269,6 +295,7 @@ const props = defineProps<{
availablePersons?: Person[];
availableMotives: Motive[];
resultCount: number;
ticketFilterParams: TicketFilters | null;
}>();
// Emits
@@ -276,74 +303,66 @@ const emit = defineEmits<{
"filters-changed": [filters: TicketFilterParams];
}>();
const filtersInitValues: TicketFilters = {
byPerson: props.ticketFilterParams?.byPerson ?? [],
byCreator: props.ticketFilterParams?.byCreator ?? [],
byAddressee: props.ticketFilterParams?.byAddressee ?? [],
byCurrentState: props.ticketFilterParams?.byCurrentState ?? ["open"],
byCurrentStateEmergency:
props.ticketFilterParams?.byCurrentStateEmergency ?? [],
byMotives: props.ticketFilterParams?.byMotives ?? [],
byCreatedAfter: props.ticketFilterParams?.byCreatedAfter ?? "",
byCreatedBefore: props.ticketFilterParams?.byCreatedBefore ?? "",
byResponseTimeExceeded:
props.ticketFilterParams?.byResponseTimeExceeded ?? false,
byAddresseeToMe: props.ticketFilterParams?.byAddresseeToMe ?? false,
byTicketId: props.ticketFilterParams?.byTicketId ?? null,
};
// État réactif
const filters = ref<TicketFilters>({
byCurrentState: ["open"],
byCurrentStateEmergency: [],
byCreatedAfter: "",
byCreatedBefore: "",
byResponseTimeExceeded: false,
byAddresseeToMe: false,
byTicketId: null,
});
const filters = ref<TicketFilters>({ ...filtersInitValues });
const byCreatedAfterTime = ref("00:00");
const byCreatedBeforeTime = ref("23:59");
const isClosedToggled = ref(false);
const isEmergencyToggled = ref(false);
// Sélection des personnes
const selectedPersons = ref<Person[]>([]);
const availablePersons = ref<Person[]>(props.availablePersons || []);
// Sélection des utilisateur assigné
const selectedAddressees = ref<UserGroupOrUser[]>([]);
// Séléction des créateurs
const selectedCreator = ref<User[]>([]);
// Sélection des motifs
const selectedMotive = ref<Motive | null>();
const selectedMotives = ref<Motive[]>([]);
// Watchers pour les sélecteurs
watch(selectedMotive, (newMotive) => {
if (newMotive && !selectedMotives.value.find((m) => m.id === newMotive.id)) {
selectedMotives.value.push(newMotive);
if (
newMotive &&
!filters.value.byMotives.find((m) => m.id === newMotive.id)
) {
filters.value.byMotives = [...filters.value.byMotives, newMotive];
}
});
// Computed pour les IDs des personnes sélectionnées
const selectedPersonIds = computed(() =>
selectedPersons.value.map((person) => person.id),
filters.value.byPerson.map((person) => person.id),
);
// Computed pour les IDs des utilisateur ou groupes sélectionnées
const selectedUserAddresseesIds = computed(() =>
selectedAddressees.value
filters.value.byAddressee
.filter((addressee) => addressee.type === "user")
.map((addressee) => addressee.id),
);
const selectedGroupAddresseesIds = computed(() =>
selectedAddressees.value
filters.value.byAddressee
.filter((addressee) => addressee.type === "user_group")
.map((addressee) => addressee.id),
);
// Computed pour les IDs des créateurs
const selectedCreatorIds = computed(() =>
selectedCreator.value.map((creator) => creator.id),
filters.value.byCreator.map((creator) => creator.id),
);
// Computed pour les IDs des motifs sélectionnés
const selectedMotiveIds = computed(() =>
selectedMotives.value.map((motive) => motive.id),
filters.value.byMotives.map((motive) => motive.id),
);
// Nouveaux états pour les toggles
const isClosedToggled = ref(false);
const isEmergencyToggled = ref(false);
// Méthodes pour gérer les toggles
const handleStateToggle = (value: boolean) => {
if (value) {
filters.value.byCurrentState = ["closed"];
@@ -379,12 +398,9 @@ const getMotiveDisplayName = (motive: Motive): string => {
};
const removeMotive = (motiveToRemove: Motive): void => {
const index = selectedMotives.value.findIndex(
(m) => m.id === motiveToRemove.id,
filters.value.byMotives = filters.value.byMotives.filter(
(m) => m.id !== motiveToRemove.id,
);
if (index !== -1) {
selectedMotives.value.splice(index, 1);
}
if (selectedMotive.value && motiveToRemove.id == selectedMotive.value.id) {
selectedMotive.value = null;
}
@@ -447,22 +463,12 @@ const applyFilters = (): void => {
};
const resetFilters = (): void => {
filters.value = {
byCurrentState: ["open"],
byCurrentStateEmergency: [],
byCreatedAfter: "",
byCreatedBefore: "",
byResponseTimeExceeded: false,
byAddresseeToMe: false,
byTicketId: null,
};
selectedPersons.value = [];
selectedCreator.value = [];
selectedAddressees.value = [];
selectedMotives.value = [];
filters.value = { ...filtersInitValues };
selectedMotive.value = null;
isClosedToggled.value = false;
isEmergencyToggled.value = false;
byCreatedAfterTime.value = "00:00";
byCreatedBeforeTime.value = "23:59";
applyFilters();
};

View File

@@ -97,7 +97,6 @@ import { useStore } from "vuex";
defineProps<{
tickets: TicketSimple[];
hasMoreTickets: boolean;
title: string;
}>();
const emit = defineEmits<{

View File

@@ -3,10 +3,12 @@ import { createApp } from "vue";
import { store } from "../TicketApp/store";
import VueToast from "vue-toast-notification";
import "vue-toast-notification/dist/theme-sugar.css";
import { TicketFilters } from "../../types";
declare global {
interface Window {
title: string;
ticketFilterParams: TicketFilters;
}
}

View File

@@ -0,0 +1,32 @@
{% extends "@ChillPerson/Person/layout.html.twig" %}
{% set ticketTitle = 'chill_ticket.list.title_with_name'|trans({'%name%': person|chill_entity_render_string }) %}
{% set activeRouteKey = 'chill_person_ticket_list' %}
{% set ticketFilterParams = {
'byPerson': [person]
} %}
{% block title %}{{ ticketTitle }}{% endblock %}
{% block css %}
{{ parent() }}
{{ encore_entry_link_tags('vue_ticket_list') }}
{% endblock %}
{% block js %}
{{ parent() }}
<script type="text/javascript">
window.ticketFilterParams = {{ ticketFilterParams|serialize|raw }};
</script>
{{ encore_entry_script_tags('vue_ticket_list') }}
{% endblock %}
{% block content %}
<h1>{{ ticketTitle }}</h1>
<div id="ticketList"></div>
<ul class="record_actions sticky-form-buttons">
<li>
<a href="{{ chill_path_add_return_path('chill_ticket_createticket__invoke') }}" class="btn btn-create">{{ 'Create'|trans }}</a>
</li>
</ul>
{% endblock %}

View File

@@ -1,4 +1,6 @@
{% extends '@ChillMain/layout.html.twig' %}
{% set ticketTitle = 'chill_ticket.list.title'|trans %}
{% block title %}{{ ticketTitle }}{% endblock %}
{% block css %}
{{ parent() }}
@@ -7,13 +9,11 @@
{% block js %}
{{ parent() }}
<script type="text/javascript">
window.title = "{{ 'chill_ticket.list.title'|trans|escape('js') }}";
</script>
{{ encore_entry_script_tags('vue_ticket_list') }}
{% endblock %}
{% block content %}
<h1>{{ ticketTitle }}</h1>
<div id="ticketList"></div>
<ul class="record_actions sticky-form-buttons">

View File

@@ -1,7 +1,9 @@
restore: Restaurer
chill_ticket:
list:
title: Tickets
title: "Tickets"
title_with_name: "{name, select, null {Tickets} undefined {Tickets} other {Tickets de {name}}}"
title_menu: "Tickets de l'usager"
title_previous_tickets: "{name, select, other {Précédent ticket de {name}} undefined {Précédent ticket}}"
no_tickets: "Aucun ticket"
loading_ticket: "Chargement des tickets..."