Complete Rewrite

This commit is contained in:
Jérémy Munsch 2021-04-18 20:31:07 +02:00
parent b078b03be1
commit 59b6b04f8a
38 changed files with 1005 additions and 473 deletions

View file

@ -2,8 +2,10 @@
namespace App\Console\Commands;
use App\Models\Project;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Facades\Hash;
class CreateUser extends Command
@ -47,7 +49,16 @@ class CreateUser extends Command
if ($this->confirm('Is the user an admin?')) {
$admin = true;
} else {
$projectId = $this->ask("What's the id of the project (s)he's working on?");
$project = null;
try {
$project = Project::where(
'id',
$this->ask("What's the id of the project (s)he's working on?")
)->firstOrFail();
} catch (ModelNotFoundException $e) {
$this->error('The project does not exists');
return -1;
}
}
$user = new User();
@ -56,11 +67,12 @@ class CreateUser extends Command
if ($admin) {
$user->role = 1;
} else {
$user->project_id = $projectId;
$user->project_id = $project->id;
}
$user->password = Hash::make($pwd);
$user->save();
$this->info('User created.');
return 0;
}
}

View file

@ -0,0 +1,34 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnum;
/**
* @method static self new()
* @method static self inprogress()
* @method static self processed()
* @method static self archived()
*/
class TicketStatus extends BaseEnum
{
protected static function values(): array
{
return [
'new' => 1,
'inprogress' => 2,
'processed' => 3,
'archived' => 4,
];
}
protected static function labels(): array
{
return [
'new' => trans('Nouveau'),
'inprogress' => trans('En cours'),
'processed' => trans('Traité'),
'archived' => trans('Archivé'),
];
}
}

28
app/Enums/TicketType.php Normal file
View file

@ -0,0 +1,28 @@
<?php
namespace App\Enums;
use Kwaadpepper\Enum\BaseEnum;
/**
* @method static self bug()
* @method static self evolution()
*/
class TicketType extends BaseEnum
{
protected static function values(): array
{
return [
'bug' => 0,
'evolution' => 1,
];
}
protected static function labels(): array
{
return [
'bug' => trans('Bug'),
'evolution' => trans('Évolution'),
];
}
}

View file

@ -2,22 +2,29 @@
namespace App\Events;
use App\Models\Ticket;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class NotifySubscriptions
{
use Dispatchable, InteractsWithSockets, SerializesModels;
use Dispatchable;
use InteractsWithSockets;
use SerializesModels;
public $ticket, $corpsMaj;
/** @var Ticket */
public $ticket;
/** @var string */
public $corpsMaj;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct($ticket, $corpsMaj)
public function __construct(Ticket $ticket, string $corpsMaj)
{
$this->ticket = $ticket;
$this->corpsMaj = $corpsMaj;

View file

@ -9,6 +9,11 @@ use App\Models\Ticket;
class ProjectController extends Controller
{
public function __construct()
{
$this->authorizeResource(Project::class, 'project');
}
public function index()
{
$projects = Project::all();

View file

@ -2,234 +2,160 @@
namespace App\Http\Controllers;
use App\Enums\TicketStatus;
use App\Enums\TicketType;
use App\Events\NotifySubscriptions;
use App\Events\TicketStatusChanged;
use App\Http\Requests\ChangeTicketStatus;
use App\Http\Requests\CreateMessage;
use App\Http\Requests\SortTickets;
use App\Http\Requests\StoreTicket;
use App\Http\Requests\UpdateTicket;
use App\Mail\NewTicket;
use App\Models\Message;
use App\Models\Project;
use App\Models\Ticket;
use App\Models\User;
use Illuminate\Http\Request;
use Exception;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Str;
class TicketController extends Controller
{
private $status = [1 => "Nouveau", 2 => "En cours", 3 => "Traité", 4 => "Archivé"];
public function index(Request $request)
public function __construct()
{
if (Auth::check()) {
$tickets = Ticket::where('status', '<>', 4);
$this->authorizeResource(Ticket::class, 'ticket');
}
$sortway = $request->sort_way == 'asc' ? 'asc' : null;
$sortway = $request->sort_way == 'desc' ? 'desc' : null;
public function index(SortTickets $request)
{
$tickets = Ticket::query();
if ($request->sort_col and $sortway) {
switch ($request->sort_col) {
case 'id':
$tickets = $tickets->orderBy('id', $sortway);
break;
case 'project':
$tickets = $tickets->orderBy('project_id', $sortway);
break;
case 'created_at':
$tickets = $tickets->orderBy('created_at', $sortway);
break;
case 'created_by':
$tickets = $tickets->orderBy('created_by', $sortway);
break;
case 'type':
$tickets = $tickets->orderBy('type', $sortway);
break;
case 'status':
$tickets = $tickets->orderBy('status', $sortway);
break;
}
}
$tickets = $tickets->paginate();
$status = $this->status;
return view('home', compact('tickets', 'status'));
if ($request->status) {
$tickets = $tickets->where('status', TicketStatus::archived());
} else {
return view('home');
$tickets = $tickets->where('status', '<>', TicketStatus::archived());
}
if ($request->sort_col and $request->sort_way) {
switch ($request->sort_col) {
case 'id':
$tickets = $tickets->orderBy('id', $request->sort_way);
break;
case 'project':
$tickets = $tickets->orderBy('project_id', $request->sort_way);
break;
case 'created_at':
$tickets = $tickets->orderBy('created_at', $request->sort_way);
break;
case 'created_by':
$tickets = $tickets->orderBy('created_by', $request->sort_way);
break;
case 'type':
$tickets = $tickets->orderBy('type', $request->sort_way);
break;
case 'status':
$tickets = $tickets->orderBy('status', $request->sort_way);
break;
}
}
$tickets = $tickets->paginate();
return view('ticket.index', compact('tickets'));
}
public function show($id) {
$ticket = Ticket::find($id);
$status = $this->status;
public function show(Ticket $ticket)
{
$messages = $ticket->messages()->orderBy('created_at', 'DESC')->get();
$isSubscribed = false;
if (!$ticket)
abort(404);
if ($ticket->subscriptions()->where('user_id', Auth::user()->id)->count() !== 0)
$isSubscribed = true;
return view('ticket.show', compact('ticket', 'status', 'messages', 'isSubscribed'));
$isSubscribed = (bool)$ticket->subscriptions()->where('user_id', Auth::user()->id)->count();
return view('ticket.show', compact('ticket', 'messages', 'isSubscribed'));
}
public function create() {
public function create()
{
$projects = Project::all();
if (!count($projects)) {
return redirect()->route('project.create')
->with('warning', 'Vous devez tout d\'abord créer un projet pour y ajouter un ticket');
}
return view('ticket.create', compact('projects'));
}
public function store(Request $request) {
$request->validate([
'text' => 'required|string',
'file_ticket' => 'mimes:jpeg,jpg,png,pdf|max:2048',
'file_ticket2' => 'mimes:jpeg,jpg,png,pdf|max:2048',
]);
public function store(StoreTicket $request)
{
$project = Project::findOrFail($request->project_id);
if (Auth::user()->role === 1) {
$projectId = $request->project_id;
} else {
$projectId = Auth::user()->project_id;
$ticket = new Ticket($request->validated());
if ($request->file_ticket) {
$ticket->file = Ticket::saveFile($project, $ticket, $ticket->type, $request->file_ticket);
}
if ($request->file_ticket2) {
$ticket->file2 = Ticket::saveFile($project, $ticket, $ticket->type, $request->file_ticket2);
}
$type = $request->type;
$text = $request->text;
$fileTicket = $request->file_ticket;
$fileTicket2 = $request->file_ticket2;
$ticket = new Ticket();
$ticket->project_id = $projectId;
$ticket->type = $type;
$ticket->text = $text;
$ticket->created_by = Auth::user()->id;
$ticket->user()->associate(auth()->user());
$ticket->save();
if ($fileTicket !== null) {
$projectSlug = Str::slug(Project::find($projectId)->name);
$fileName = $projectSlug . "-" . ($type === "0" ? "bug" : "evo") . "-" . $ticket->id . "." . $fileTicket->extension();
$fileTicket->move(public_path('fichiers/' . $projectSlug . "/" . $ticket->id . "/"), $fileName);
$filePath = 'fichiers/' . $projectSlug . "/" . $ticket->id . "/" . $fileName;
if (config('mail.MAIL_ADMIN')) {
Mail::to(config('mail.MAIL_ADMIN'))->send(new NewTicket($ticket));
} else {
throw new Exception('Missing MAIL_ADMIN in .env');
}
if ($fileTicket2 !== null) {
$projectSlug = Str::slug(Project::find($projectId)->name);
$fileName2 = $projectSlug . "-" . ($type === "0" ? "bug" : "evo") . "-" . $ticket->id . "-2." . $fileTicket2->extension();
$fileTicket2->move(public_path('fichiers/' . $projectSlug . "/" . $ticket->id . "/"), $fileName2);
$filePath2 = 'fichiers/' . $projectSlug . "/" . $ticket->id . "/" . $fileName2;
}
if ($fileTicket !== null)
$ticket->file = $filePath;
if ($fileTicket2 !== null)
$ticket->file2 = $filePath2;
if ($fileTicket !== null || $fileTicket2 !== null)
$ticket->save();
Mail::to(config('mail.MAIL_ADMIN'))->send(new NewTicket($ticket));
return redirect()->route('home')->with('success', 'Ticket enregistré !');
}
public function edit($id) {
$ticket = Ticket::findOrFail($id);
if ($ticket->created_by === Auth::user()->id || Auth::user()->role === 1) {
$projects = Project::all();
$status = $this->status;
return view('ticket.edit', compact('ticket', 'projects', 'status'));
}
return redirect()->back();
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClass
public function edit(Ticket $ticket)
{
$projects = Project::all();
return view('ticket.edit', compact('ticket', 'projects'));
}
public function update(Request $request, $id) {
$request->validate([
'text' => 'required|string',
'file_ticket' => 'mimes:jpeg,jpg,png,pdf|max:2048',
'file_ticket2' => 'mimes:jpeg,jpg,png,pdf|max:2048',
]);
public function update(UpdateTicket $request, Ticket $ticket)
{
$project = Project::findOrFail($request->project_id);
$ticket->fill($request->validated());
if (Auth::user()->role === 1) {
$projectId = $request->project_id;
$status = $request->status;
} else {
$projectId = Auth::user()->project_id;
if ($request->file_ticket) {
if (File::exists($ticket->file)) {
File::delete($ticket->file);
}
$ticket->file = Ticket::saveFile($project, $ticket, $ticket->type, $request->file_ticket);
}
$type = $request->type;
$text = $request->text;
$fileTicket = $request->file_ticket;
$fileTicket2 = $request->file_ticket2;
$ticket = Ticket::findOrFail($id);
if ($fileTicket !== null) {
if ($ticket->file !== null && file_exists(public_path() . $ticket->file))
unlink(public_path() . $ticket->file);
$projectSlug = Str::slug(Project::find($projectId)->name);
$fileName = $projectSlug . "-" . ($type === "0" ? "bug" : "evo") . "-" . $ticket->id . "." . $fileTicket->extension();
$fileTicket->move(public_path('fichiers/' . $projectSlug . "/" . $ticket->id . "/"), $fileName);
$filePath = 'fichiers/' . $projectSlug . "/" . $ticket->id . "/" . $fileName;
}
if ($fileTicket2 !== null) {
if ($ticket->file2 !== null && file_exists(public_path() . $ticket->file2))
unlink(public_path() . $ticket->file2);
$projectSlug = Str::slug(Project::find($projectId)->name);
$fileName2 = $projectSlug . "-" . ($type === "0" ? "bug" : "evo") . "-" . $ticket->id . "." . $fileTicket2->extension();
$fileTicket2->move(public_path('fichiers/' . $projectSlug . "/" . $ticket->id . "/"), $fileName2);
$filePath2 = 'fichiers/' . $projectSlug . "/" . $ticket->id . "/" . $fileName2;
}
$ticket->project_id = $projectId;
$ticket->type = $type;
$ticket->text = $text;
if ($fileTicket !== null)
$ticket->file = $filePath;
if ($fileTicket2 !== null)
$ticket->file2 = $filePath2;
if (Auth::user()->role === 1) {
$ticket->status = $status;
event(new TicketStatusChanged($ticket, $this->status));
if ($request->file_ticket2) {
if (File::exists($ticket->file2)) {
File::delete($ticket->file2);
}
$ticket->file2 = Ticket::saveFile($project, $ticket, $ticket->type, $request->file_ticket2);
}
$ticket->save();
return redirect()->route('edit-ticket', $ticket->id)->with('success', 'Modifications enregistrées !');
return redirect()->route('ticket.edit', $ticket->id)->with('success', 'Modifications enregistrées !');
}
public function changeStatus(Request $request, $id) {
$ticket = Ticket::find($id);
if (!$ticket)
abort(404);
$ticket->status = $request->status;
public function changeStatus(ChangeTicketStatus $request, Ticket $ticket)
{
$ticket->fill($request->validated());
$ticket->save();
event(new TicketStatusChanged($ticket, $this->status));
event(new NotifySubscriptions($ticket, "Le ticket à changé de status : " . $this->status[$ticket->status]));
return back();
}
public function createMessage(Request $request) {
$request->validate([
'message' => 'required',
]);
if ($request->file_message !== null) {
$request->validate([
'file_message' => 'mimes:jpeg,jpg,png,pdf|max:2048',
]);
}
$ticketId = $request->ticket_id;
public function createMessage(CreateMessage $request, Ticket $ticket)
{
$fileMessage = $request->file_message;
$ticket = Ticket::find($ticketId);
if (Auth::user()->role !== 1 && (!$ticket || Auth::user()->id !== $ticket->created_by))
return back()->withErrors("Les données entrées sont incorrects.");
$message = new Message();
$message->ticket_id = $ticketId;
$message->user_id = Auth::user()->id;
$message->message = $request->message;
$message = new Message($request->validated());
$message->user()->associate(auth()->user());
$message->ticket()->associate($ticket);
$message->save();
if ($request->file_message !== null) {
if ($request->file_message) {
$projectSlug = Str::slug($ticket->project->name);
$fileMessage = $request->file_message;
$fileName = $projectSlug . '-ticket-' . $ticket->id . '-message-' . $message->id . '.' . $fileMessage->extension();
@ -239,13 +165,19 @@ class TicketController extends Controller
$message->save();
}
event(new NotifySubscriptions($ticket, "Nouvelle réponse de " . $message->user->name . " :<br>" . $message->message));
return back();
}
public function subscribe(Request $request, $id) {
User::find(Auth::user()->id)->subscriptions()->toggle($id);
public function setToArchive(Ticket $ticket)
{
$ticket->status = TicketStatus::archived();
$ticket->saveOrFail();
return redirect()->route('ticket.index');
}
public function subscribe(Ticket $ticket)
{
User::find(Auth::user()->id)->subscriptions()->toggle($ticket->id);
return back();
}
}

View file

@ -0,0 +1,31 @@
<?php
namespace App\Http\Requests;
use App\Enums\TicketStatus;
use Illuminate\Foundation\Http\FormRequest;
class ChangeTicketStatus extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'status' => [
'required',
// @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
function ($attribute, $value, $fail) {
if (!in_array($value, TicketStatus::toValues())) {
$fail(__('Veuillez choisir entre :values', [
'value' => implode(',', TicketStatus::toLabels())
]));
}
}
],
];
}
}

View file

@ -0,0 +1,21 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class CreateMessage extends FormRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'message' => 'required|min:10',
'file_message' => 'sometimes|mimes:jpeg,jpg,png,pdf|max:2048'
];
}
}

View file

@ -0,0 +1,42 @@
<?php
namespace App\Http\Requests;
use App\Enums\TicketStatus;
use Illuminate\Foundation\Http\FormRequest;
class SortTickets extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return auth()->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'sort_way' => 'sometimes|in:asc,desc',
'status' => [
'sometimes',
// @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
function ($attribute, $value, $fail) {
if (!in_array($value, TicketStatus::toValues())) {
$fail(__('Veuillez choisir entre :values', [
'value' => implode(',', TicketStatus::toLabels())
]));
}
}
],
];
}
}

View file

@ -0,0 +1,63 @@
<?php
namespace App\Http\Requests;
use App\Enums\TicketStatus;
use App\Enums\TicketType;
use Illuminate\Foundation\Http\FormRequest;
class StoreTicket extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
if (auth()->user()->project_id) {
$this->merge([
'project_id' => auth()->user()->project_id,
'status' => TicketStatus::new()->value,
]);
}
return auth()->check();
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'project_id' => 'required|exists:projects,id',
'type' => [
'required',
// @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
function ($attribute, $value, $fail) {
if (!in_array($value, TicketType::toValues())) {
$fail(__('Veuillez choisir entre :values', [
'value' => implode(',', TicketType::toLabels())
]));
}
}
],
'status' => [
'required',
// @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundInExtendedClassBeforeLastUsed
function ($attribute, $value, $fail) {
if (!in_array($value, TicketStatus::toValues())) {
$fail(__('Veuillez choisir entre :values', [
'value' => implode(',', TicketStatus::toLabels())
]));
}
}
],
'text' => 'required|string|min:40',
'file_ticket' => 'mimes:jpeg,jpg,png,pdf|max:2048',
'file_ticket2' => 'mimes:jpeg,jpg,png,pdf|max:2048',
];
}
}

View file

@ -0,0 +1,21 @@
<?php
namespace App\Http\Requests;
use App\Enums\TicketStatus;
class UpdateTicket extends StoreTicket
{
public function authorize()
{
if (auth()->user()->project_id) {
$this->merge([
'project_id' => auth()->user()->project_id,
'status' => TicketStatus::new()->value,
]);
}
return auth()->check() or
auth()->user()->role === 1 or
request()->route()->ticket->created_by === auth()->user()->id;
}
}

View file

@ -27,7 +27,7 @@ class AddStatusMessages
{
$message = new Message();
$message->ticket_id = $event->ticket->id;
$message->message = "Le status est passé à : " . $event->status[$event->ticket->status];
$message->message = "Le status est passé à : " . $event->ticket->status->label;
$message->save();
}
}

View file

@ -12,16 +12,6 @@ use Illuminate\Support\Facades\Mail;
class SendNotificationMail
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
@ -30,8 +20,10 @@ class SendNotificationMail
*/
public function handle(NotifySubscriptions $event)
{
foreach($event->ticket->subscriptions as $sub)
if ($sub->email !== Auth::user()->email)
foreach ($event->ticket->subscriptions as $sub) {
if ($sub->email !== Auth::user()->email) {
Mail::to($sub->email)->send(new TicketUpdate($event->ticket, $event->corpsMaj));
}
}
}
}

View file

@ -2,18 +2,55 @@
namespace App\Models;
use App\Enums\TicketType;
use App\Events\NotifySubscriptions;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\UploadedFile;
class Message extends Model
{
use HasFactory;
public function ticket() {
return $this->belongsTo('App\Models\Ticket', 'ticket_id');
protected $fillable = [
'message'
];
protected static function booted()
{
static::created(function (Message $message) {
event(new NotifySubscriptions(
$message->ticket,
trans('Nouvelle réponse de :name :<br> :message', [
'name' => $message->user->name,
'message' => $message->message
])
));
});
}
public function user() {
return $this->belongsTo('App\Models\User', 'user_id');
public function ticket()
{
return $this->belongsTo(Ticket::class, 'ticket_id');
}
public function user()
{
return $this->belongsTo(User::class, 'user_id');
}
public function saveFile(Project $project, Ticket $ticket, UploadedFile $file)
{
$projectAssetFullPath = Project::ticketAssetsFullPath($project->slug_name);
$fileAssetPath = Project::ticketAssetsPath($project->slug_name);
$fileName = sprintf(
'%s-%s-%d-%d.%s',
$project->slug_name,
$ticket->id,
rand(),
$file->extension()
);
$file->move(sprintf('%s/messages/%d', $projectAssetFullPath, $ticket->id), $fileName);
return sprintf('%s/%d/%s', $fileAssetPath, $ticket->id, $fileName);
}
}

View file

@ -16,15 +16,15 @@ class Project extends Model
protected static function booted()
{
static::created(function (Project $project) {
$path = Project::ticketAssetsPath($project->slug_name);
$path = Project::ticketAssetsFullPath($project->slug_name);
if (!File::exists($path)) {
File::makeDirectory($path);
}
});
static::updating(function (Project $project) {
$oldName = Project::where('id', $project->id)->first()->name;
$oldPath = Project::ticketAssetsPath($oldName);
$newPath = Project::ticketAssetsPath($project->slug_name);
$oldPath = Project::ticketAssetsFullPath($oldName);
$newPath = Project::ticketAssetsFullPath($project->slug_name);
if (File::exists($oldPath)) {
File::move($oldPath, $newPath);
} else {
@ -32,7 +32,7 @@ class Project extends Model
}
});
static::deleted(function (Project $project) {
$path = Project::ticketAssetsPath($project->slug_name);
$path = Project::ticketAssetsFullPath($project->slug_name);
if (File::exists($path)) {
File::deleteDirectory($path);
}
@ -41,7 +41,7 @@ class Project extends Model
public function tickets()
{
return $this->hasMany('App\Models\Ticket', 'project_id');
return $this->hasMany(Ticket::class, 'project_id');
}
public function getSlugNameAttribute()
@ -49,8 +49,13 @@ class Project extends Model
return Str::slug($this->name);
}
protected static function ticketAssetsPath(string $str)
public static function ticketAssetsFullPath(string $str)
{
return public_path(sprintf('fichiers/%s', $str));
return public_path(self::ticketAssetsPath($str));
}
public static function ticketAssetsPath(string $str)
{
return sprintf('fichiers/%s', $str);
}
}

View file

@ -2,26 +2,79 @@
namespace App\Models;
use App\Enums\TicketStatus;
use App\Enums\TicketType;
use App\Events\NotifySubscriptions;
use App\Events\TicketStatusChanged;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\UploadedFile;
use Kwaadpepper\Enum\Traits\CastsEnums;
class Ticket extends Model
{
use HasFactory;
use CastsEnums;
public function project() {
return $this->belongsTo('App\Models\Project', 'project_id');
protected $enumCasts = [
'type' => TicketType::class,
'status' => TicketStatus::class
];
protected $fillable = [
'project_id',
'type',
'status',
'text'
];
protected static function booted()
{
static::updating(function (Ticket $ticket) {
$oldStatus = Ticket::where('id', $ticket->id)->first()->status;
if (!$ticket->status->equals($oldStatus)) {
event(new TicketStatusChanged($ticket, $oldStatus));
event(new NotifySubscriptions($ticket, sprintf(
'Le ticket à changé de status : %s',
$ticket->status->label
)));
}
});
}
public function user() {
return $this->belongsTo('App\Models\User', 'created_by');
public function project()
{
return $this->belongsTo(Project::class);
}
public function messages() {
return $this->hasMany('App\Models\Message', 'ticket_id');
public function user()
{
return $this->belongsTo(User::class, 'created_by');
}
public function subscriptions() {
return $this->belongsToMany('App\Models\User', 'subscriptions');
public function messages()
{
return $this->hasMany(Message::class, 'ticket_id');
}
public function subscriptions()
{
return $this->belongsToMany(User::class, 'subscriptions');
}
public static function saveFile(Project $project, Ticket $ticket, TicketType $type, UploadedFile $file)
{
$projectAssetFullPath = Project::ticketAssetsFullPath($project->slug_name);
$fileAssetPath = Project::ticketAssetsPath($project->slug_name);
$fileName = sprintf(
'%s-%s-%d-%d.%s',
$project->slug_name,
$type->getDefinition(),
$ticket->id,
rand(),
$file->extension()
);
$file->move(sprintf('%s/%d', $projectAssetFullPath, $ticket->id), $fileName);
return sprintf('%s/%d/%s', $fileAssetPath, $ticket->id, $fileName);
}
}

View file

@ -2,14 +2,14 @@
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use HasFactory, Notifiable;
use HasFactory;
use Notifiable;
/**
* The attributes that are mass assignable.
@ -41,15 +41,23 @@ class User extends Authenticatable
'email_verified_at' => 'datetime',
];
public function tickets() {
return $this->hasMany("App\Models\Ticket", 'created_by');
public function tickets()
{
return $this->hasMany(Ticket::class, 'created_by');
}
public function messages() {
return $this->hasMany('App\Models\Messages', 'user_id');
public function messages()
{
return $this->hasMany(Messages::class, 'user_id');
}
public function subscriptions() {
return $this->belongsToMany('App\Models\Ticket', 'subscriptions');
public function subscriptions()
{
return $this->belongsToMany(Ticket::class, 'subscriptions');
}
public function project()
{
return $this->belongsTo(Project::class);
}
}

View file

@ -0,0 +1,99 @@
<?php
namespace App\Policies;
use App\Models\Project;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
class ProjectPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*
* @param \App\Models\User $user
* @return mixed
*/
public function viewAny(User $user)
{
return $user->role === 1;
}
/**
* Determine whether the user can view the model.
*
* @param \App\Models\User $user
* @param \App\Models\Project $project
* @return mixed
*/
//phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
public function view(User $user, Project $project)
{
return $user->role === 1;
}
/**
* Determine whether the user can create models.
*
* @param \App\Models\User $user
* @return mixed
*/
public function create(User $user)
{
return $user->role === 1;
}
/**
* Determine whether the user can update the model.
*
* @param \App\Models\User $user
* @param \App\Models\Project $project
* @return mixed
*/
//phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
public function update(User $user, Project $project)
{
return $user->role === 1;
}
/**
* Determine whether the user can delete the model.
*
* @param \App\Models\User $user
* @param \App\Models\Project $project
* @return mixed
*/
//phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
public function delete(User $user, Project $project)
{
return $user->role === 1;
}
/**
* Determine whether the user can restore the model.
*
* @param \App\Models\User $user
* @param \App\Models\Project $project
* @return mixed
*/
//phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
public function restore(User $user, Project $project)
{
return $user->role === 1;
}
/**
* Determine whether the user can permanently delete the model.
*
* @param \App\Models\User $user
* @param \App\Models\Project $project
* @return mixed
*/
//phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
public function forceDelete(User $user, Project $project)
{
return $user->role === 1;
}
}

View file

@ -0,0 +1,123 @@
<?php
namespace App\Policies;
use App\Enums\TicketStatus;
use App\Models\Ticket;
use App\Models\User;
use Illuminate\Auth\Access\HandlesAuthorization;
use Illuminate\Auth\Access\Response;
use Illuminate\Support\Facades\Auth;
class TicketPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view any models.
*
* @param \App\Models\User $user
* @return mixed
*/
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
public function viewAny(User $user)
{
return true;
}
/**
* Determine whether the user can view the model.
*
* @param \App\Models\User $user
* @param \App\Models\Ticket $ticket
* @return mixed
*/
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
public function view(User $user, Ticket $ticket)
{
return true;
}
/**
* Determine whether the user can create models.
*
* @param \App\Models\User $user
* @return mixed
*/
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
public function create(User $user)
{
return true;
}
/**
* Determine whether the user can update the model.
*
* @param \App\Models\User $user
* @param \App\Models\Ticket $ticket
* @return mixed
*/
public function update(User $user, Ticket $ticket)
{
return Auth::user()->role === 1 or $ticket->created_by == $user->id;
}
/**
* Determine whether the user can delete the model.
*
* @param \App\Models\User $user
* @param \App\Models\Ticket $ticket
* @return mixed
*/
//phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
public function delete(User $user, Ticket $ticket)
{
return false;
}
/**
* Determine whether the user can restore the model.
*
* @param \App\Models\User $user
* @param \App\Models\Ticket $ticket
* @return mixed
*/
public function restore(User $user, Ticket $ticket)
{
return Auth::user()->role === 1 or $ticket->created_by == $user->id;
}
/**
* Determine whether the user can permanently delete the model.
*
* @param \App\Models\User $user
* @param \App\Models\Ticket $ticket
* @return mixed
*/
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
public function forceDelete(User $user, Ticket $ticket)
{
return false;
}
public function changeStatus(User $user)
{
return $user->role === 1;
}
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
public function suscribeToTicket(User $user, Ticket $ticket)
{
return !$ticket->status->equals(TicketStatus::archived());
}
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
public function createMessage(User $user, Ticket $ticket)
{
if ($user->role !== 1 and $ticket->created_by !== $user->id) {
return Response::deny(trans('Vous ne pouvez modifier un ticket qui ne vous appartient pas.'));
}
return $ticket->status->equals(TicketStatus::archived()) ?
Response::deny(trans('Vous ne pouvez ajouter un message à un ticket qui est archivé')) : Response::allow();
}
}

View file

@ -2,18 +2,22 @@
namespace App\Providers;
use App\Models\Ticket;
use App\Policies\ProjectPolicy;
use App\Policies\TicketPolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
// 'App\Models\Model' => 'App\Policies\ModelPolicy',
Project::class => ProjectPolicy::class,
Ticket::class => TicketPolicy::class
];
/**

View file

@ -226,7 +226,8 @@ return [
'URL' => Illuminate\Support\Facades\URL::class,
'Validator' => Illuminate\Support\Facades\Validator::class,
'View' => Illuminate\Support\Facades\View::class,
'TicketType' => App\Enums\TicketType::class,
'TicketStatus' => App\Enums\TicketStatus::class
],
];

View file

View file

@ -6,3 +6,13 @@
// Bootstrap
@import '~bootstrap/scss/bootstrap';
a.col_sort {
&:link,:visited,:hover,:active {
text-decoration: none;
}
}
.container-xxl {
max-width: 1340px !important;
}

View file

@ -1,114 +1,8 @@
@extends('layout.app')
@section('tickets', 'active')
@section('content')
<div class="container">
@auth
<h2>Gestion des tickets<a href="{{ route('create-ticket') }}" class="float-right btn btn-sm btn-primary">Créer un ticket</a></h2>
<table class="table table-hover">
<thead>
<th scope="col">
#&nbsp;
@include('modules.sorter', ['colname' => 'id'])
</th>
<th scope="col">
Projet&nbsp;
@include('modules.sorter', ['colname' => 'project'])
</th>
<th scope="col">
Type&nbsp;
@include('modules.sorter', ['colname' => 'type'])</th>
<th scope="col">Fichier</th>
<th scope="col">Fichier 2</th>
<th scope="col">
Date de création&nbsp;
@include('modules.sorter', ['colname' => 'created_at'])</th>
<th scope="col">
Créé par&nbsp;
@include('modules.sorter', ['colname' => 'created_by'])</th>
<th scope="col">
Status&nbsp;
@include('modules.sorter', ['colname' => 'status'])
</th>
<th scope="col">Contenu</th>
@if (Auth::user()->role === 1)
<th></th>
@endif
</thead>
<tbody>
@foreach ($tickets as $ticket)
@if (Auth::user()->project_id === $ticket->project->id || Auth::user()->role === 1)
<tr>
<th scope="row">
<a href="{{ route('show-ticket', $ticket->id) }}" class="btn btn-sm btn-outline-secondary">{{ $ticket->id }}</a>
</th>
<td>{{ $ticket->project->name }}</td>
<td>
@if ($ticket->type === 0)
<span class="badge badge-danger">Bug</span>
@else
<span class="badge badge-success">Évolution</span>
@endif
</td>
<td>
@if ($ticket->file !== null)
<a href="{{ asset($ticket->file) }}"
class="btn btn-sm btn-outline-secondary" target="_blank">Fichier</a>
@endif
</td>
<td>
@if ($ticket->file2 !== null)
<a href="{{ asset($ticket->file2) }}"
class="btn btn-sm btn-outline-secondary" target="_blank">Fichier 2</a>
@endif
</td>
<td>{{ Carbon\Carbon::parse($ticket->created_at)->format('d/m/Y à H:i') }}</td>
<td>{{ $ticket->user->name }}</td>
<td>{{ $status[$ticket->status] }}</td>
<td>
{{-- Modal du ticket --}}
<!-- Button trigger modal -->
<button type="button" class="btn btn-sm btn-outline-secondary" data-toggle="modal" data-target="#ticket{{ $ticket->id }}Modal">
Aperçu
</button>
<!-- Modal -->
<div class="modal fade" id="ticket{{ $ticket->id }}Modal" tabindex="-1" aria-labelledby="ticket{{ $ticket->id }}ModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="ticket{{ $ticket->id }}ModalLabel">
{{ $ticket->project->name }} - Ticket N°{{ $ticket->id }} - {{ $ticket->type === 0 ? "Bug" : "Évolution" }}
</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>{!! nl2br(e($ticket->text)) !!}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-sm btn-primary" data-dismiss="modal">Fermer</button>
</div>
</div>
</div>
</div>
</td>
<td>
@if (Auth::user()->role === 1)
<a href="{{ route('edit-ticket', $ticket->id) }}" class="btn btn-sm btn-outline-primary">Modifier</a>
@endif
</td>
</tr>
@endif
@endforeach
</tbody>
</table>
{{ $tickets->withQueryString()->links() }}
@else
<h2>Bienvenue sur le support LDC WEB.</h2>
<p>Pour consulter vos tickets, merci de vous connecter : <a href="{{ route('login') }}">Connexion</a></p>
@endauth
</div>
@endsection

View file

@ -12,14 +12,18 @@
<nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm">
<div class="container">
<a class="navbar-brand" href="{{ url('/') }}">
@if(auth()->user()->project)
Projet : <b class="text-primary">{{ ucwords(auth()->user()->project->name) }}</b>
@else
{{ config('app.name', 'Laravel') }}
@endif
</a>
@auth
@if (Auth::user()->role === 1)
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link @yield('tickets')" href="{{ route('home') }}">Tickets</a>
<a class="nav-link @yield('tickets')" href="{{ route('ticket.index') }}">Tickets</a>
</li>
<li class="nav-item">
<a class="nav-link @yield('project')" href="{{ route('project.index') }}">Projets</a>
@ -50,8 +54,8 @@
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="#"
onclick="event.preventDefault();
document.getElementById('logout-form').submit();">
onclick="event.preventDefault();
document.getElementById('logout-form').submit();">
Déconnexion
</a>

View file

@ -9,4 +9,4 @@
Description :<br>
{!! nl2br(e($ticket->text)) !!}
</p>
<p>Lien vers le ticket : <a href="{{ route('show-ticket', $ticket->id) }}" traget="_blank">Ouvrir dans un nouvel onglet</a></p>
<p>Lien vers le ticket : <a href="{{ route('ticket.show', $ticket->id) }}" traget="_blank">Ouvrir dans un nouvel onglet</a></p>

View file

@ -6,4 +6,4 @@
<li>Créer par : {{ $ticket->user->name }}</li>
</ul>
<p>{!! $corpsMaj !!}</p>
<p>Lien vers le ticket : <a href="{{ route('show-ticket', $ticket->id) }}" target="_blank">Ouvrir dans un nouvel onglet</a></p>
<p>Lien vers le ticket : <a href="{{ route('ticket.show', $ticket->id) }}" target="_blank">Ouvrir dans un nouvel onglet</a></p>

View file

@ -7,3 +7,8 @@
</ul>
</div>
@endif
@if(session('warning'))
<div class="alert alert-warning">
{{ session('warning') }}
</div>
@endif

View file

@ -1,6 +1,6 @@
@if(!request('sort_way') or request('sort_col') !== $colname or (request('sort_way') === 'desc' and request('sort_col') === $colname))
<a class="col_sort asc" href="{{ route('home', ['sort_col' => $colname, 'sort_way' => 'asc']) }}">&darr;</a>
<a class="col_sort text-secondary lead asc" href="{{ route('ticket.index', ['sort_col' => $colname, 'sort_way' => 'asc']) }}">&nbsp;&darr;&nbsp;</a>
@endif
@if(!request('sort_way') or request('sort_col') !== $colname or (request('sort_way') === 'asc' and request('sort_col') === $colname))
<a class="col_sort desc" href="{{ route('home', ['sort_col' => $colname, 'sort_way' => 'desc']) }}">&uarr;</a>
<a class="col_sort text-secondary lead desc" href="{{ route('ticket.index', ['sort_col' => $colname, 'sort_way' => 'desc']) }}">&nbsp;&uarr;&nbsp;</a>
@endif

View file

@ -11,11 +11,8 @@
<div class="card-body">
<form method="POST" action="{{ route('project.store') }}">
@csrf
@include('modules.error-messages')
<div class="form-group">
<input type="text" class="form-control" name="name" placeholder="Nom du projet *" value="{{ old('name') }}">
</div>
@include('project.form-inputs')
<div class="form-group text-center">
<input type="submit" class="btn btn-primary" value="Enregistrer">

View file

@ -12,16 +12,13 @@
<form method="POST" action="{{ route('project.update', $project->id) }}">
@csrf
@method('PUT')
@include('modules.error-messages')
@if (session('success'))
<div class="alert alert-success">
{!! session('success') !!}
</div>
@endif
<div class="form-group">
<input type="text" class="form-control" name="name" placeholder="Nom du projet *" value="{{ $project->name }}">
</div>
@include('project.form-inputs')
<div class="form-group text-center">
<input type="submit" class="btn btn-primary" value="Modifier">

View file

@ -0,0 +1,4 @@
@include('modules.error-messages')
<div class="form-group">
<input type="text" class="form-control" name="name" placeholder="Nom du projet *" value="{{ old('name') ?? $project->name }}">
</div>

View file

@ -9,44 +9,10 @@
<div class="card">
<div class="card-header">Créer un ticket<a href="{{ route('home') }}" class="float-right btn btn-sm btn-primary">Retour</a></div>
<div class="card-body">
<form method="POST" action="{{ route('store-ticket') }}" enctype="multipart/form-data">
@method('PUT')
<form method="POST" action="{{ route('ticket.store') }}" enctype="multipart/form-data">
@csrf
@include('modules.error-messages')
<div class="form-row">
<div class="form-group col-md-6">
<label for="project" class="col-form-label">Projet</label>
<select class="custom-select" name="project_id" id="project" @if (Auth::user()->role !== 1) disabled @endif>
@foreach($projects as $project)
@if (Auth::user()->role === 1)
<option value="{{ $project->id }}" @if (old('project_id') == $project->id)selected @endif>{{ $project->name }}</option>
@elseif ($project->id === Auth::user()->project_id)
<option value="{{ $project->id }}" selected>{{ $project->name }}</option>
@endif
@endforeach
</select>
</div>
<div class="form-group col-md-6">
<label for="type" class="col-form-label">Type</label>
<select class="custom-select" name="type" id="type">
<option value="0" @if (old('type') == 0)selected @endif>Bug</option>
<option value="1" @if (old('type') == 1)selected @endif>Évolution</option>
</select>
</div>
</div>
<div class="form-group">
<label for="text" class="col-form-label text-md-right">Description</label>
<textarea class="form-control" name="text" id="text" placeholder="Description du ticket *">{{ old('text') }}</textarea>
</div>
<div class="form-group">
<input type="file" name="file_ticket" accept="image/png, image/jpeg, image/jpg, .pdf">
</div>
<div class="form-group">
<input type="file" name="file_ticket2" accept="image/png, image/jpeg, image/jpg, .pdf">
</div>
@include('ticket.form-inputs')
<div class="form-group text-center">
<input type="submit" class="btn btn-primary" value="Enregistrer">

View file

@ -9,73 +9,14 @@
<div class="card">
<div class="card-header">Modifier un ticket<a href="{{ route('home') }}" class="float-right btn btn-sm btn-primary">Retour</a></div>
<div class="card-body">
<form method="POST" action="{{ route('update-ticket', $ticket->id) }}" enctype="multipart/form-data">
<form method="POST" action="{{ route('ticket.update', $ticket->id) }}" enctype="multipart/form-data">
@csrf
@include('modules.error-messages')
@method('PUT')
@if (session('success'))
<div class="alert alert-success">{!! session('success') !!}</div>
<div class="alert alert-success">{!! session('success') !!}</div>
@endif
<div class="form-row">
<div class="form-group col">
<label for="project" class="col-form-label">Projet</label>
<select class="custom-select" name="project_id" id="project" @if (Auth::user()->role !== 1) disabled @endif>
@foreach($projects as $project)
@if (Auth::user()->role === 1)
<option value="{{ $project->id }}" @if ($ticket->project->id == $project->id)selected @endif>{{ $project->name }}</option>
@elseif ($project->id === Auth::user()->project_id)
<option value="{{ $project->id }}" selected>{{ $project->name }}</option>
@endif
@endforeach
</select>
</div>
<div class="form-group col">
<label for="type" class="col-form-label">Type</label>
<select class="custom-select" name="type" id="type">
<option value="0" @if ($ticket->type == 0)selected @endif>Bug</option>
<option value="1" @if ($ticket->type == 1)selected @endif>Évolution</option>
</select>
</div>
@if (Auth::user()->role === 1)
<div class="form-group col">
<label for="status" class="col-form-label">Status</label>
<select class="custom-select" name="status" id="status">
@for ($i = 1; $i < count($status) + 1; $i++)
<option value="{{ $i }}" @if ($ticket->status === $i) selected @endif>{{ $status[$i] }}</option>
@endfor
</select>
</div>
@else
<div class="col">
<p>Status</p>
<p>{{ $status[$ticket->status] }}</p>
</div>
@endif
</div>
<div class="form-group">
<label for="text" class="col-form-label text-md-right">Description</label>
<textarea class="form-control" name="text" id="text" placeholder="Description du ticket *">{{ $ticket->text }}</textarea>
</div>
<div class="form-group">
<input type="file" name="file_ticket" accept="image/png, image/jpeg, image/jpg, .pdf">
@if ($ticket->file !== null)
<hr>
<div class="text-center">
<img src="{{ asset($ticket->file) }}" style="max-width: 100%;">
</div>
@endif
</div>
<div class="form-group">
<input type="file" name="file_ticket2" accept="image/png, image/jpeg, image/jpg, .pdf">
@if ($ticket->file2 !== null)
<hr>
<div class="text-center">
<img src="{{ asset($ticket->file2) }}" style="max-width: 100%;">
</div>
@endif
</div>
@include('ticket.form-inputs')
<div class="form-group text-center">
<input type="submit" class="btn btn-primary" value="Modifier">

View file

@ -0,0 +1,60 @@
@include('modules.error-messages')
<div class="form-row">
@can('changeStatus')
<div class="form-group col">
<label for="project" class="col-form-label">{{ __('Projet') }}</label>
<select class="custom-select" name="project_id" id="project" @if (Auth::user()->role !== 1) disabled @endif>
@foreach($projects as $project)
@if (Auth::user()->role === 1)
<option value="{{ $project->id }}" @if ($project->id == old('project_id') or (isset($ticket) and $ticket->project->id == $project->id))selected @endif>{{ $project->name }}</option>
@elseif ($project->id === Auth::user()->project_id)
<option value="{{ $project->id }}" selected>{{ $project->name }}</option>
@endif
@endforeach
</select>
</div>
@endcan
<div class="form-group col">
<label for="type" class="col-form-label">{{ __('Type') }}</label>
<select class="custom-select" name="type" id="type">
@foreach(TicketType::toArray() as $ticketType)
<option value="{{ $ticketType->value }}" @if($ticketType->value == old('type') or (isset($ticket) and $ticketType->equals($ticket->type)))selected @endif>{{ $ticketType->label }}</option>
@endforeach
</select>
</div>
@can('changeStatus')
<div class="form-group col">
<label for="status" class="col-form-label">Status</label>
<select class="custom-select" name="status" id="status">
@foreach(TicketStatus::toArray() as $ticketStatus)
<option value="{{ $ticketStatus->value }}" @if($ticketType->value == old('status') or (isset($ticket) and $ticketStatus->equals($ticket->status)))selected @endif>{{ $ticketStatus->label }}</option>
@endforeach
</select>
</div>
@endcan
</div>
<div class="form-group">
<label for="text" class="col-form-label text-md-right">{{ __('Description') }}</label>
<textarea class="form-control" name="text" id="text" placeholder="{{ __('Description du ticket') }} *">{{ old('text') ?? $ticket->text ?? ''}}</textarea>
</div>
<div class="form-group">
<input type="file" name="file_ticket" accept="image/png, image/jpeg, image/jpg, .pdf">
@if (isset($ticket) and $ticket->file !== null)
<hr>
<div class="text-center">
<img src="{{ asset($ticket->file) }}" style="max-width: 100%;">
</div>
@endif
</div>
<div class="form-group">
<input type="file" name="file_ticket2" accept="image/png, image/jpeg, image/jpg, .pdf">
@if (isset($ticket) and $ticket->file2 !== null)
<hr>
<div class="text-center">
<img src="{{ asset($ticket->file2) }}" style="max-width: 100%;">
</div>
@endif
</div>

View file

@ -0,0 +1,125 @@
@extends('layout.app')
@section('tickets', 'active')
@section('content')
<div class="container container-xxl">
<h2>
Gestion des tickets @if(request()->status) (Archivés) @endif
<a href="{{ route('ticket.create') }}" class="float-right btn btn-sm btn-primary">Créer un ticket</a>
@if(request()->status)
<a href="{{ route('ticket.index') }}" class="float-right btn btn-sm btn-success mr-2">Tickets en cours</a>
@else
<a href="{{ route('ticket.index', ['status' => TicketStatus::archived()]) }}" class="float-right btn btn-sm btn-secondary mr-2">Tickets Archivés</a>
@endif
</h2>
<table class="table table-hover">
<thead>
<th scope="col">
#&nbsp;
@include('modules.sorter', ['colname' => 'id'])
</th>
<th scope="col">
Projet&nbsp;
@include('modules.sorter', ['colname' => 'project'])
</th>
<th scope="col">
Type&nbsp;
@include('modules.sorter', ['colname' => 'type'])</th>
<th scope="col">Fichier</th>
<th scope="col">Fichier 2</th>
<th scope="col">
Date de création&nbsp;
@include('modules.sorter', ['colname' => 'created_at'])</th>
<th scope="col">
Créé par&nbsp;
@include('modules.sorter', ['colname' => 'created_by'])</th>
<th scope="col">
Statut&nbsp;
@include('modules.sorter', ['colname' => 'status'])
</th>
<th scope="col">Contenu</th>
@if (Auth::user()->role === 1)
<th>Actions</th>
@endif
</thead>
<tbody>
@foreach ($tickets as $ticket)
<tr>
<th scope="row">
{{ $ticket->id }}
</th>
<td>{{ $ticket->project->name }}</td>
<td>
@if ($ticket->type->equals(TicketType::bug()))
<span class="badge badge-danger">{{ $ticket->type->label }}</span>
@else
<span class="badge badge-success">{{ $ticket->type->label }}</span>
@endif
</td>
<td>
@if ($ticket->file !== null)
<a href="{{ asset($ticket->file) }}"
class="btn btn-sm btn-outline-secondary" target="_blank">Fichier</a>
@endif
</td>
<td>
@if ($ticket->file2 !== null)
<a href="{{ asset($ticket->file2) }}"
class="btn btn-sm btn-outline-secondary" target="_blank">Fichier 2</a>
@endif
</td>
<td>{{ Carbon\Carbon::parse($ticket->created_at)->format('d/m/Y à H:i') }}</td>
<td>{{ $ticket->user->name }}</td>
<td>{{ $ticket->status->label }}</td>
<td>
{{-- Modal du ticket --}}
<!-- Button trigger modal -->
<button type="button" class="btn btn-sm btn-outline-secondary" data-toggle="modal" data-target="#ticket{{ $ticket->id }}Modal">
Aperçu
</button>
<!-- Modal -->
<div class="modal fade" id="ticket{{ $ticket->id }}Modal" tabindex="-1" aria-labelledby="ticket{{ $ticket->id }}ModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="ticket{{ $ticket->id }}ModalLabel">
{{ $ticket->project->name }} - Ticket N°{{ $ticket->id }} - {{ $ticket->type === 0 ? "Bug" : "Évolution" }}
</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<p>{!! nl2br(e($ticket->text)) !!}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-sm btn-primary" data-dismiss="modal">Fermer</button>
</div>
</div>
</div>
</div>
</td>
<td>
<a href="{{ route('ticket.show', $ticket->id) }}" class="btn btn-sm btn-outline-secondary">{{ __('Messages') }}</a>
@can('update', $ticket)
<a href="{{ route('ticket.edit', $ticket) }}" class="btn btn-sm btn-outline-primary">Modifier</a>
@endcan
@can('delete', $ticket)
@if(!$ticket->status->equals(TicketStatus::archived()))
<form method="POST" action="{{ route('ticket.archive', $ticket) }}">
@method('PUT')
@csrf
<button type="submit" class="btn btn-sm btn-outline-danger" onclick="return confirm('Êtes-vous sûr(e) ?')">Archiver</button>
</form>
@endif
@endcan
</td>
</tr>
@endforeach
</tbody>
</table>
{{ $tickets->withQueryString()->links() }}
</div>
@endsection

View file

@ -3,30 +3,32 @@
@section('content')
<div class="container">
<div class="form-row">
<h2 class="@if (Auth::user()->role === 1)col-md-8 @else col-md-10 @endif">
<h2 class="@can('suscribeToTicket', $ticket)col-md-8 @else col-md-10 @endcan">
<a href="{{ route('home') }}" class="text-decoration-none">←</a>
Ticket n°{{ $ticket->id }} : {{ $ticket->project->name }} - {{ $ticket->type === 0 ? "Bug" : "Évolution" }}
@if (Auth::user()->role !== 1)
<span class="badge badge-secondary">{{ $status[$ticket->status] }}</span>
@endif
Ticket n°{{ $ticket->id }} : {{ $ticket->project->name }} - {{ $ticket->type->label }}
@cannot('createMessage', $ticket)
<span class="badge badge-secondary">{{ $ticket->status->label }}</span>
@endcan
</h2>
<form action="{{ route('subscribe-ticket', $ticket->id) }}" method="POST" id="form_abonnement" class="col-md-2">
@can('suscribeToTicket', $ticket)
<form action="{{ route('ticket.subscribe', $ticket->id) }}" method="POST" id="form_abonnement" class="col">
@csrf
<div class="custom-control custom-checkbox text-right p-2">
<input type="checkbox" name="subscription" class="custom-control-input" id="subscription" @if ($isSubscribed)checked @endif onchange="$('#form_abonnement').submit()">
<label class="custom-control-label" for="subscription">Suivre le ticket</label>
</div>
</form>
@if (Auth::user()->role === 1)
<form action="{{ route('change-status-ticket', $ticket->id) }}" method="POST" id="form_status" class="col-md-2">
@endcan
@can('changeStatus', $ticket)
<form action="{{ route('ticket.changeStatus', $ticket->id) }}" method="POST" id="form_status" class="col">
@csrf
<select class="custom-select" name="status" id="status" onchange="$('#form_status').submit();">
@for ($i = 1; $i < count($status) + 1; $i++)
<option value="{{ $i }}" @if ($ticket->status === $i) selected @endif>{{ $status[$i] }}</option>
@endfor
@foreach(TicketStatus::toArray() as $ticketStatus)
<option value="{{ $ticketStatus->value }}" @if($ticketStatus->equals($ticket->status))selected @endif>{{ $ticketStatus->label }}</option>
@endforeach
</select>
</form>
@endif
@endcan
</div>
<hr>
<div>
@ -41,16 +43,14 @@
@endif
<h4>
Suivi
@if (Auth::user()->id === $ticket->created_by || Auth::user()->role === 1)
@can('createMessage', $ticket)
<button type="button" class="btn btn-sm btn-primary float-right" data-toggle="modal" data-target="#messageModal">
Répondre
</button>
{{-- Modal --}}
<div class="modal fade" id="messageModal" tabindex="-1" aria-labelledby="messageModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<form action="{{ route('create-message') }}" method="POST" enctype="multipart/form-data">
<form action="{{ route('ticket.message.create', $ticket) }}" method="POST" enctype="multipart/form-data">
@method('PUT')
@csrf
<input type="hidden" name="ticket_id" value="{{ $ticket->id }}">
@ -74,8 +74,7 @@
</div>
</div>
</div>
{{-- Fin modal --}}
@endif
@endcan
</h4>
@foreach ($messages as $message)
@if ($message->user_id === null)

View file

@ -16,20 +16,32 @@ use Illuminate\Support\Facades\Route;
|
*/
Route::get('/', [TicketController::class, 'index'])->name("home");
Route::get('/', function () {
if (auth()->check()) {
return redirect()->route('ticket.index');
}
return view('home');
})->name("home");
Route::get('/login', [LoginController::class, 'index'])->name('login');
Route::post('/login', [LoginController::class, 'authenticate'])->name('authenticate');
Route::middleware(['auth'])->group(function () {
Route::post('/logout', [LoginController::class, 'logout'])->name('logout');
// Ticket
Route::get('/ticket/create', [TicketController::class, 'create'])->name('create-ticket');
Route::put('/ticket/create', [TicketController::class, 'store'])->name('store-ticket');
Route::get('/ticket/{id}/edit', [TicketController::class, 'edit'])->name('edit-ticket');
Route::post('/ticket/{id}/edit', [TicketController::class, 'update'])->name('update-ticket');
Route::get('/ticket/{id}', [TicketController::class, 'show'])->name('show-ticket');
Route::post('/ticket/change-status/{id}', [TicketController::class, 'changeStatus'])->name('change-status-ticket');
Route::put('/ticket/message/create', [TicketController::class, 'createMessage'])->name('create-message');
Route::post('/ticket/subscribe/{id}', [TicketController::class, 'subscribe'])->name('subscribe-ticket');
Route::resource('ticket', TicketController::class)->except(['destroy']);
Route::prefix('/ticket')->name('ticket.')->group(function () {
Route::post('/{ticket}/status', [TicketController::class, 'changeStatus'])
->middleware('can:changeStatus,ticket')
->name('changeStatus');
Route::post('/{ticket}/subscribe', [TicketController::class, 'subscribe'])
->middleware('can:suscribeToTicket,ticket')
->name('subscribe');
Route::put('/{ticket}/archive', [TicketController::class, 'setToArchive'])
->middleware('can:changeStatus,ticket')
->name('archive');
Route::put('/{ticket}/message/create', [TicketController::class, 'createMessage'])
->middleware('can:createMessage,ticket')
->name('message.create');
});
// Projets
Route::middleware(['is-admin'])->group(function () {
Route::resource('project', ProjectController::class)->except('show');