feat: ✨ set priority on tickets with order functionnality #75

This commit is contained in:
Alexis Gousseau 2025-04-08 09:47:13 +02:00
parent 64b95f6f28
commit f932818c45
12 changed files with 185 additions and 79 deletions

View file

@ -13,6 +13,7 @@ use App\Http\Requests\Bo\ChangeTicketStatus;
use App\Http\Requests\Bo\CreateMessage;
use App\Http\Requests\Bo\SortTickets;
use App\Http\Requests\Bo\StoreTicket;
use App\Http\Requests\Bo\UpdateChangeOrderRequest;
use App\Http\Requests\Bo\UpdateMessage;
use App\Http\Requests\Bo\UpdateTicket;
use App\Lib\Helpers\FileStorageHelper;
@ -29,12 +30,10 @@ use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Str;
use App\Traits\Controllers\ChangesModelOrder;
use Illuminate\Support\Arr;
class TicketController extends Controller
{
use ChangesModelOrder;
/**
* TicketController
*/
@ -508,6 +507,7 @@ class TicketController extends Controller
Session::remove('tickets.st');
Session::remove('tickets.pr');
Session::remove('tickets.ty');
Session::remove('tickets.or');
Session::remove('tickets.search');
}
if ($request->assr) {
@ -522,11 +522,15 @@ class TicketController extends Controller
if ($request->tys) {
Session::remove('tickets.ty');
}
if ($request->ord) {
Session::remove('tickets.or');
}
if ($request->pr) {
Session::put('tickets.pr', $request->pr);
}
if ($request->st) {
Session::put('tickets.st', $request->st);
Session::remove('tickets.or');
}
if ($request->ty) {
Session::put('tickets.ty', $request->ty);
@ -534,6 +538,9 @@ class TicketController extends Controller
if ($request->ass) {
Session::put('tickets.ass', $request->ass);
}
if ($request->or && Session::has('tickets.pr')) {
Session::put('tickets.or', $request->or);
}
Session::save();
if (Session::has('tickets.pr')) {
@ -553,6 +560,13 @@ class TicketController extends Controller
if (Session::has('tickets.ty')) {
$tickets->where('type', Session::get('tickets.ty'));
}
if (Session::has('tickets.or')) {
$tickets->orderBy('order', 'ASC');
} else {
$tickets->orderBy('last_activity', 'DESC');
}
return $tickets;
}
@ -709,4 +723,62 @@ class TicketController extends Controller
}
return \sprintf("<br><br> Fichier %d : <br>%s<br>", $fileNb, $file);
}
/**
* Change order.
*
* @param \App\Http\Requests\Bo\UpdateChangeOrderRequest $request
* @param mixed ...$params
* @return \Illuminate\Http\RedirectResponse
* @throws \RuntimeException Si le nom du parametre de la route ne correspond pas au params utilisé.
*/
public function changeOrder(UpdateChangeOrderRequest $request, mixed ...$params): \Illuminate\Http\RedirectResponse
{
$paramNames = collect(request()->route()->parameterNames);
$className = Str::of($paramNames->get($paramNames->count() - 2))->lower()->studly();
$className = "\\App\\Models\\{$className}";
$model = Arr::get($params, $paramNames->count() - 2);
if (!class_exists($className)) {
throw new \RuntimeException("The {$className} class doesn't exist.");
}
if (!\is_object($model)) {
throw new \RuntimeException("The second last param isn't an object.");
}
if (get_class($model) !== (string)Str::of($className)->replaceStart('\\', '')) {
throw new \RuntimeException("The {$className} doesn't match second last binded param.");
}
/** @var \Illuminate\Database\Eloquent\Model|null */
$model = $className::query()->where($model->getRouteKeyName(), $model->getRouteKey())->first();
if (!$model) {
return back()->with('error', trans('crud.messages.order_not_changed'));
}
return DB::transaction(function () use ($request, $className, $model) {
/** @var \Illuminate\Support\Collection $models */
$models = $className::query()
->orderBy('order', 'ASC')
->where('site_id', $model->site_id)
->where('status', '!=', TicketStatusEnum::archived)
->where('id', '!=', $model->getKey())
->where('order', $request->direction ? '<' : '>', $model->order)
->get();
/** @var \Illuminate\Database\Eloquent\Model|null $tmp */
$tmp = $request->direction ? $models->last() : $models->first();
if (!$tmp) {
return back()->with('error', trans('crud.messages.order_not_changed'));
}
$newOrder = $tmp->getOriginal('order');
$tmp->update(['order' => $model->getOriginal('order')]);
$model->update(['order' => $newOrder]);
return redirect()->back()->with('success', trans('crud.messages.order_changed'));
});
}
}

View file

@ -223,10 +223,6 @@ class Controller extends BaseController
) {
$query = $query->orderBy(\sprintf("$table.%s", request()->sort_col), request()->sort_way);
}
} elseif (Schema::hasColumn($table, 'order')) {
$query = $query->orderBy('order', 'ASC');
Session::put("$rName.sort_col", 'order');
Session::put("$rName.sort_way", 'asc');
}
return $query;
}

View file

@ -1,8 +1,7 @@
<?php
use App\Models\Ticket;
use Illuminate\Support\Facades\Schema;
use App\Enums\Tickets\TicketStatusEnum;
use App\Models\Site;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
@ -16,16 +15,14 @@ return new class extends Migration
public function up(): void
{
Schema::table('tickets', function (Blueprint $table) {
$table->integer('order')->after('status')->comment('Ordre du ticket');
$table->integer('order')->after('status')->comment('Ticket order.');
});
Ticket::query()->where('status', '!=', TicketStatusEnum::archived)->get()->map(function ($ticket, $ticketIndex) {
$ticket->order = $ticketIndex + 1;
$ticket->saveQuietly();
});
Ticket::query()->where('status', '=', TicketStatusEnum::archived)->get()->map(function ($ticket, $ticketIndex) {
$ticket->order = $ticketIndex + 1;
$ticket->saveQuietly();
Site::query()->get()->map(function ($site) {
$site->tickets->map(function ($ticket, $ticketIndex) {
$ticket->order = $ticketIndex + 1;
$ticket->saveQuietly();
});
});
}

View file

@ -50,5 +50,6 @@
"Verify Your Email Address": "Verify Your Email Address",
"Whoops!": "Whoops!",
"You are logged in!": "You are logged in!",
"You are receiving this email because we received a password reset request for your account.": "You are receiving this email because we received a password reset request for your account."
}
"You are receiving this email because we received a password reset request for your account.": "You are receiving this email because we received a password reset request for your account.",
"see_mode": "see :mode mode"
}

View file

@ -214,4 +214,12 @@ return [
'username' => 'username',
'year' => 'year',
],
'custom' => [
'type' => 'type',
'tickets' => 'tickets',
'assigned_to' => 'assigned to',
'order' => 'order',
'priority' => 'priority',
'default' => 'default',
]
];

View file

@ -55,5 +55,6 @@
"Verify Your Email Address": "Vérifiez votre adresse e-mail",
"Whoops!": "Oups !",
"You are logged in!": "Vous êtes connecté·e !",
"You are receiving this email because we received a password reset request for your account.": "Vous recevez cet e-mail car nous avons reçu une demande de réinitialisation de mot de passe pour votre compte."
}
"You are receiving this email because we received a password reset request for your account.": "Vous recevez cet e-mail car nous avons reçu une demande de réinitialisation de mot de passe pour votre compte.",
"see_mode": "voir mode :mode"
}

View file

@ -255,5 +255,7 @@ return [
'tickets' => 'étiquettes',
'assigned_to' => 'assigné à',
'order' => 'ordre',
'priority' => 'priorité',
'default' => 'défaut',
]
];

View file

@ -33,6 +33,7 @@ import {
faDeleteLeft,
faDiagramProject,
faDownload,
faEquals,
faEraser,
faEye,
faEyeSlash,
@ -60,6 +61,7 @@ import {
faTicket,
faTimes,
faTrash,
faTriangleExclamation,
faUndoAlt,
faUsers,
faWandMagic,
@ -137,4 +139,6 @@ library.add(faCopy);
library.add(faClipboard);
library.add(faXmark);
library.add(faArrowRightFromBracket);
library.add(faTriangleExclamation);
library.add(faEquals);
dom.watch();

View file

@ -1,4 +1,7 @@
<td class="text-center align-middle">
@if (isset($value))
{{ $value }}
@endif
@can('changeOrder', $model)
<div class="d-flex justify-content-center align-items-center">
<form @class(['invisible' => $loop->first and $models->onFirstPage()])
@ -30,7 +33,7 @@
</button>
</form>
</div>
@else
@elseif(isset($noRightsVisible) && $noRightsVisible)
<span class="text-danger" data-bs-toggle="tooltip" title="{{ __('crud.other.right') }}">
<i class="fa-solid fa-ban"></i>
</span>

View file

@ -12,21 +12,7 @@
@endphp
<tr class="table-col-sorter border-start-0 border-end-0 border-top-0 border-secondary border border-2">
@foreach ($cols as $col => $colname)
@if($col === 'order')
@if(
!request()->sort_col
&& !request()->sort_way
&& !request()->ass
&& !request()->st
&& !request()->pr
&& !request()->ty
&& !request()->search
)
@include('bo.modules.table-col-sorter-cell')
@endif
@else
@include('bo.modules.table-col-sorter-cell')
@endif
@include('bo.modules.table-col-sorter-cell')
@endforeach
{{-- Buttons Col --}}
<th class="text-end" scope="col">
@ -42,4 +28,4 @@
</span>
@endif
</th>
</tr>
</tr>

View file

@ -13,7 +13,9 @@
<div class="col">
<div class="card">
<div class="card-header">
<h2 class="d-inline">{{ __('Gestion des tickets') }} @if (Session::get('tickets.st') == \App\Enums\Tickets\TicketStatusEnum::archived->value)
<h2 class="d-inline">
{{ __('Gestion des tickets') }}
@if (Session::get('tickets.st') == \App\Enums\Tickets\TicketStatusEnum::archived->value)
(Archivés)
@endif
</h2>
@ -24,23 +26,39 @@
@can('isAdmin')
<a class="btn btn-info" href="{{ route('bo.labels.index') }}"><i class="fa-solid fa-tags"></i></a>
@endcan
<a class="btn btn-primary" href="{{ route('bo.ticket.create') }}"><i
class="fa-solid fa-square-plus"></i>&nbsp;&nbsp;{{ __('ticket') }}</a>
<a class="btn btn-primary" href="{{ route('bo.ticket.create') }}">
<i class="fa-solid fa-square-plus"></i>&nbsp;&nbsp;{{ __('ticket') }}
</a>
@if (session('tickets.pr') && session('tickets.st') != \App\Enums\Tickets\TicketStatusEnum::archived->value)
@if (session('tickets.or'))
<a class="btn btn-info float-end" href="{{ route('bo.ticket.index', ['ord' => -1]) }}">
<i class="fa-solid fa-equals"></i>&nbsp;&nbsp;{{ __('see_mode', ['mode' => __('validation.custom.default')]) }}
</a>
@else
<a class="btn btn-danger" href="{{ route('bo.ticket.index', ['or' => 1]) }}">
<i class="fa-solid fa-triangle-exclamation"></i>&nbsp;&nbsp;{{ __('see_mode', ['mode' => __('validation.custom.priority')]) }}
</a>
@endif
@endif
@if (session('tickets.st') == \App\Enums\Tickets\TicketStatusEnum::archived->value)
<a class="btn btn-success" href="{{ route('bo.ticket.index', ['sts' => -1]) }}"><i
class="fa-solid fa-inbox"></i></a>
<a class="btn btn-success" href="{{ route('bo.ticket.index', ['sts' => -1]) }}">
<i class="fa-solid fa-inbox"></i>
</a>
@else
<a class="btn btn-secondary float-end"
href="{{ route('bo.ticket.index', ['st' => \App\Enums\Tickets\TicketStatusEnum::archived]) }}"><i
class="fa-solid fa-box-archive"></i></a>
href="{{ route('bo.ticket.index', ['st' => \App\Enums\Tickets\TicketStatusEnum::archived]) }}">
<i class="fa-solid fa-box-archive"></i>
</a>
@endif
<a class="btn btn-dark" href="{{ route('bo.ticket.index', ['rst' => 1]) }}"><i
class="fa-solid fa-eraser"></i>&nbsp;{{ __(' le tri') }}</a>
<a class="btn btn-dark" href="{{ route('bo.ticket.index', ['rst' => 1]) }}">
<i class="fa-solid fa-eraser"></i>&nbsp;{{ __(' le tri') }}
</a>
</div>
<div class="float-end me-2">
<div class="input-group">
<label class="input-group-text" for="selectProject"><i
class="fa-solid fa-diagram-project"></i></label>
<label class="input-group-text" for="selectProject">
<i class="fa-solid fa-diagram-project"></i>
</label>
<select class="form-select" id="selectProject">
<option value="-1">{{ __('Tous') }}</option>
@foreach ($sites as $site)
@ -137,12 +155,12 @@
</div>
</div>
<div class="col">
<form class="w-75 float-end mt-3" id="searchTicketForm"
<form class="float-end mt-3" id="searchTicketForm"
action="{{ request()->fullUrlWithQuery(['search' => '']) }}">
<div class="input-group mb-3">
<input class="form-control" id="searchTicket" name="search" type="text"
value="{{ session()->get('tickets.search') }}" aria-label="{{ __('Recherche de ticket..') }}"
aria-describedby="searchTicket" placeholder="{{ __('Recherche de ticket..') }}">
value="{{ session()->get('tickets.search') }}" aria-label="{{ __('Recherche de ticket...') }}"
aria-describedby="searchTicket" placeholder="{{ __('Recherche de ticket...') }}">
<button class="btn btn-outline-danger" id="searchDelete" type="button"><i
class="fa-solid fa-delete-left"></i></button>
<button class="btn btn-outline-primary" type="submit"><i
@ -165,17 +183,35 @@
<div class="overflow-scroll w-100">
<table class="table-hover table">
<thead>
@include('bo.modules.table-col-sorter', [
'cols' => [
@php
$cols = [];
if (session('tickets.or')) {
$cols = array_merge($cols, [
'order' => Str::ucfirst(__('validation.custom.priority'))
]);
}
$cols = array_merge($cols, [
'id' => Str::ucfirst(__('validation.attributes.id')),
'title' => Str::ucfirst(__('validation.attributes.name')),
'type' => Str::ucfirst(__('validation.custom.type')),
'tickets' => Str::ucfirst(__('validation.custom.tickets')),
'assigned_to' => Str::ucfirst(__('validation.custom.assigned_to')),
'order' => Str::ucfirst(__('validation.custom.order')),
]);
if (Auth::user()->isAdmin()) {
$cols = array_merge($cols, [
'tickets' => Str::ucfirst(__('validation.custom.tickets')),
]);
}
if (Auth::user()->isAdmin()) {
$cols = array_merge($cols, [
'assigned_to' => Str::ucfirst(__('validation.custom.assigned_to')),
]);
}
$cols = array_merge($cols, [
'created_at' => Str::ucfirst(__('validation.attributes.created_at')),
],
'ignore' => ['id', 'tickets']
]);
@endphp
@include('bo.modules.table-col-sorter', [
'cols' => $cols,
'ignore' => ['id', 'tickets', 'order']
])
</thead>
<tbody>
@ -186,8 +222,19 @@
</td>
</tr>
@endif
@foreach ($tickets as $ticket)
@foreach ($tickets as $key => $ticket)
<tr class="position-relative">
{{-- order --}}
@if (session('tickets.or'))
@include('bo.modules.change-model-order', [
'routeName' => 'ticket',
'model' => $ticket,
'models' => $tickets,
'value' => $key + 1,
'noRightsVisible' => false,
])
@endif
{{-- id --}}
<td scope="row">
<div class="d-flex justify-content-between">
<span class="me-1">{{ $ticket->id }}&nbsp;&nbsp;@if ($ticket->is_internal)
@ -209,12 +256,15 @@
</span>
</div>
</td>
{{-- title --}}
<td class="text-center">
<span class="me-1">{{ $ticket->title }}</span>
</td>
{{-- type --}}
<td class="text-center">
<span class="badge {{ $ticket->type->class() }}">{{ $ticket->type->label() }}</span>
</td>
{{-- tag --}}
@php
$routeName = request()->route()->getName();
@endphp
@ -228,24 +278,10 @@
</span>
</td>
@endcan
{{-- assigned_to --}}
@if (Auth::user()->isAdmin())
<td>{{ $ticket->assignedUser ? $ticket->assignedUser->name : __('Personne') }}</td>
@endif
@if (
!request()->sort_col
&& !request()->sort_way
&& !request()->ass
&& !request()->st
&& !request()->pr
&& !request()->ty
&& !request()->search
)
@include('bo.modules.change-model-order', [
'routeName' => 'ticket',
'model' => $ticket,
'models' => $tickets,
])
@endif
<td>{{ Carbon\Carbon::parse($ticket->created_at)->format('d/m/Y à H:i') }}</td>
<td>
<div class="d-flex flex-column">

View file

@ -95,6 +95,10 @@ Route::middleware('admin-secure')
)
->middleware('can:createGitlabIssue,ticket')
->name('ticket.create-issue');
Route::patch('/tickets/{ticket}/change-order/{direction}', [
TicketController::class,
'changeOrder'
])->where('direction', 'up|down')->name('ticket.change-order');
});
Route::resource('credit_accounts', AccountController::class)
->only(['index'])
@ -147,10 +151,6 @@ Route::middleware('admin-secure')
Route::post('/{ticket}/status', [TicketController::class, 'changeStatus'])
->middleware('can:changeStatus,ticket')
->name('changeStatus');
Route::patch('/{ticket}/change-order/{direction}', [
TicketController::class,
'changeOrder'
])->where('direction', 'up|down')->name('change-order');
Route::post('/{ticket}/subscribe', [TicketController::class, 'subscribe'])
->middleware('can:suscribeToTicket,ticket')
->name('subscribe');