mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-10-15 17:59:43 +00:00
Créer la page et la liste des tickets dans le dossier d'usager
This commit is contained in:
@@ -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();
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -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) {
|
||||
|
69
src/Bundle/ChillTicketBundle/src/Menu/PersonMenuBuilder.php
Normal file
69
src/Bundle/ChillTicketBundle/src/Menu/PersonMenuBuilder.php
Normal 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'];
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -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,
|
||||
},
|
||||
);
|
||||
|
||||
|
@@ -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();
|
||||
|
||||
|
@@ -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];
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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);
|
||||
|
@@ -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();
|
||||
};
|
||||
|
||||
|
@@ -97,7 +97,6 @@ import { useStore } from "vuex";
|
||||
defineProps<{
|
||||
tickets: TicketSimple[];
|
||||
hasMoreTickets: boolean;
|
||||
title: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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 %}
|
@@ -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">
|
||||
|
@@ -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..."
|
||||
|
Reference in New Issue
Block a user