Merge branch 'trunk'

This commit is contained in:
Vincent BRIDIER 2025-08-22 12:51:32 +02:00
commit 844928e945
34 changed files with 105 additions and 689 deletions

4
.gitignore vendored
View file

@ -22,3 +22,7 @@ Homestead.yaml
npm-debug.log
yarn-error.log
storage/samlidp/*
.npmrc
.zshrc
.npm/
.composer/

View file

@ -2,11 +2,16 @@
PROD : support.uwil.fr
RE7 : support-web.re7-s-web.fr
## Gitlab Intégration
Le token de groupe pour l'intégration est valable 1 an max
Le token de groupe pour l'intégration est valable 11 mois
Il faut le renouveler ici : https://lab.s-web.fr/groups/ldc-web/projets/-/settings/access_tokens et le nommer SW Intégration
Role : Guest
Scope : api
## TODO
- App\Models\Message:booted(): investigate why this make errors on prod,

View file

@ -1,193 +0,0 @@
<?php
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Projects\StoreProjectRequest;
use App\Http\Requests\Bo\Projects\UpdateProjectRequest;
use App\Lib\Helpers\ToolboxHelper;
use App\Models\Project;
use App\Models\Site;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class ProjectController extends Controller
{
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*/
public function index(Request $request): \Illuminate\Contracts\View\View
{
/** @var \Illuminate\Database\Eloquent\Builder $projectModels */
$projectModels = Project::query();
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$projectModels,
$search,
null,
'name',
);
}
$searchFields = trans('Nom');
/** Sort columns with a query */
$this->sortQuery($projectModels);
/** Custom pagination */
$projectModels = $this->customPaginate($projectModels, $request->pagination);
return view('bo.pages.projects.index', compact('projectModels', 'search', 'searchFields'));
}
/**
* Crud show view
*
* @param \App\Models\Project $project
* @return \Illuminate\Contracts\View\View|\Illuminate\Contracts\View\Factory
*/
public function show(Project $project)
{
return view('bo.pages.projects.show', [
'projectModel' => $project
]);
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\Project $project
* @return \Illuminate\Contracts\View\View
*/
public function create(Project $project): \Illuminate\Contracts\View\View
{
$siteModels = Site::orderBy('name', 'ASC')->get();
return view('bo.pages.projects.create', [
'projectModel' => $project,
'siteModels' => $siteModels
]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Projects\StoreProjectRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreProjectRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$project = new Project();
$project->fill($request->validated());
if ($project->saveOrFail()) {
return redirect()
->route('bo.projects.edit', $project)
->with('success', trans(
'crud.messages.has_been_created',
['model' => Str::singular(trans('models.classes.projects'))]
));
}
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_created',
['model' => Str::singular(trans('models.classes.projects'))]
));
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Project $project
* @return \Illuminate\Contracts\View\View
*/
public function edit(Project $project): \Illuminate\Contracts\View\View
{
$siteModels = Site::orderBy('name', 'ASC')->get();
return view('bo.pages.projects.edit', [
'projectModel' => $project,
'siteModels' => $siteModels
]);
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Projects\UpdateProjectRequest $request
* @param \App\Models\Project $project
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateProjectRequest $request, Project $project): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $project) {
$project->fill($request->validated());
if ($project->saveOrFail()) {
return redirect()
->route('bo.projects.edit', $project)
->with('success', trans(
'crud.messages.has_been_updated',
['model' => Str::singular(trans('models.classes.projects'))]
));
} //end if
return redirect()
->back()
->with('error', trans(
'crud.messages.cannot_be_updated',
['model' => Str::singular(trans('models.classes.projects'))]
));
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Project $project
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(Project $project): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($project) {
if ($project->deleteOrFail()) {
return redirect()
->route('bo.projects.index')
->with('success', trans(
'crud.messages.has_been_deleted',
['model' => Str::singular(trans('models.classes.projects'))]
));
}
return redirect()
->back()
->with('error', trans(
':crud.messages.cannot_be_deleted',
['model' => Str::singular(trans('models.classes.projects'))]
));
});
}
/**
* Get a paginate resource.
*
* @param \Illuminate\Http\Request $request
* @return Illuminate\Http\JsonResponse
*/
public function jsonPaginate(Request $request): \Illuminate\Http\JsonResponse
{
return DB::transaction(function () use ($request) {
$model = $request->targetModel;
$search = $request->search;
$models = $model::where("name", "LIKE", "%{$search}%")->orderBy("name", "ASC");
return ToolboxHelper::jsonPaginate($this->customPaginate($models, $request->pagination));
});
}
}

View file

@ -338,15 +338,14 @@ class TicketController extends Controller
/** @var \App\Models\User */
$user = auth()->user();
// Subscribe to ticket.
if ($request->subscribe) {
if (!$user->subscriptions()->find($ticket->id)) {
$user->subscriptions()->attach($ticket->id);
}
} elseif ($user->subscriptions()->find($ticket->id)) {
$user->subscriptions()->syncWithoutDetaching([$ticket->id]);
} else {
$user->subscriptions()->detach($ticket->id);
}
// Store files if any.
if (isset($filesMessage) and $filesMessage) {
foreach ($filesMessage as $key => $singleFile) {
if ($key === 0) {

View file

@ -6,10 +6,8 @@ use App\Models\Site;
use App\Models\User;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use App\Http\Requests\Bo\Users\StoreUserRequest;
use App\Http\Requests\Bo\Users\UpdateUserRequest;
use App\Http\Controllers\Bo\Auth\ForgotPasswordController;
@ -182,7 +180,7 @@ class UserController extends Controller
* Favourite site submit
*
* @param \App\Http\Requests\Bo\Users\FavouriteSiteRequest $request
*
*
* @return \Illuminate\Http\RedirectResponse
*/
public function favouriteSiteSubmit(FavouriteSiteRequest $request): \Illuminate\Http\RedirectResponse

View file

@ -37,6 +37,8 @@ class CreateMessage extends FormRequest
*/
protected function prepareForValidation()
{
/** @var \App\Models\User */
$user = auth()->user();
$this->merge([
'subscribe' => $this->subscribe ? true : false,
'type' => $this->is_private ? MessageTypeEnum::private->value : MessageTypeEnum::public->value

View file

@ -1,32 +0,0 @@
<?php
namespace App\Http\Requests\Bo\Projects;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
class StoreProjectRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
return Gate::allows('isAdmin');
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'name' => 'required|string|min:3|max:255|unique:projects,name',
'site_id' => 'required|exists:sites,id',
];
}
}

View file

@ -1,18 +0,0 @@
<?php
namespace App\Http\Requests\Bo\Projects;
class UpdateProjectRequest extends StoreProjectRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return array_merge(parent::rules(), [
'name' => 'required|string|min:3|max:255|unique:projects,name,' . $this->route('project')->id . ',id',
]);
}
}

View file

@ -28,7 +28,7 @@ class StoreSiteRequest extends FormRequest
'name' => 'required|string|min:3|max:255',
'users' => 'nullable|array',
'users.*.id' => 'required|exists:users,id',
'gitlab_project_id' => 'nullable|unique:sites,gitlab_project_id',
'gitlab_project_id' => 'nullable',
'project_manager_id' => 'nullable|exists:users,id',
'url_re7' => 'nullable|url',
'url_prod' => 'nullable|url'

View file

@ -4,15 +4,4 @@ namespace App\Http\Requests\Bo\Sites;
class UpdateSiteRequest extends StoreSiteRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return array_merge(parent::rules(), [
'gitlab_project_id' => 'nullable|unique:sites,gitlab_project_id,' . $this->route('site')->id . ',id',
]);
}
}

View file

@ -39,5 +39,4 @@ class UpdateChangeOrderRequest extends FormRequest
'direction' => 'required|boolean',
];
}
}

View file

@ -9,10 +9,10 @@ use Illuminate\Foundation\Http\FormRequest;
class FavouriteSiteRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Auth::check();
@ -37,4 +37,3 @@ class FavouriteSiteRequest extends FormRequest
];
}
}

View file

@ -96,14 +96,9 @@ class Message extends Model
}
// ? Is message author is admin ?
if ($messageUser->isAdmin()) {
// Url: https://supportweb.ldc.fr/ticket/583/message/create.
// SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '583-8' for key 'PRIMARY'
// $ticket->subscriptions()->attach($messageUser);
// Add subscription.
if (!$messageUser->subscriptions()->find($ticket->id)) {
$messageUser->subscriptions()->attach($ticket->id);
\session()->flash('info', trans('Vous avez été inscrit.'));
}
$messageUser->subscriptions()->syncWithoutDetaching([$ticket->id]);
\session()->flash('info', trans('Vous avez été inscrit.'));
}
// * Send mail to other subscribed users
event(new NotifySubscriptions(

View file

@ -1,37 +0,0 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
/**
* @property string $name
* @property Site $site_id
*/
class Project extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name',
'site_id'
];
// * RELATIONSHIPS.
/**
* BelongsTo Site
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function site()
{
return $this->belongsTo(Site::class);
}
}

View file

@ -136,16 +136,6 @@ class Site extends Model
return $this->belongsTo(User::class, 'project_manager_id');
}
/**
* BelongsTo Project relation
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function project()
{
return $this->belongsTo(Project::class);
}
/**
* Return value attribute for belongsto search component
*

View file

@ -72,7 +72,9 @@ class Ticket extends Model
}
});
static::updating(function (Ticket $ticket) {
if (!$ticket->original['status'] !== $ticket->status->value && $ticket->original['order'] === $ticket->order) {
if (
!$ticket->original['status'] !== $ticket->status->value && $ticket->original['order'] === $ticket->order
) {
event(new TicketStatusChanged(
$ticket,
TicketStatusEnum::make($ticket->original['status']),
@ -106,7 +108,9 @@ class Ticket extends Model
*/
private static function setOrder(self $model, bool $isArchived = false): void
{
$model->order = \intval(self::query()->where('status', $isArchived ? '=' : '!=', TicketStatusEnum::archived)->max('order')) + 1;
$model->order = \intval(
self::query()->where('status', $isArchived ? '=' : '!=', TicketStatusEnum::archived)->max('order')
) + 1;
}

View file

@ -7,7 +7,6 @@ use App\Enums\Tickets\TicketTypeEnum;
use App\Enums\Users\RoleEnum;
use App\Models\Ticket;
use App\Models\User;
use Error;
use Illuminate\Auth\Access\HandlesAuthorization;
class TicketPolicy

View file

@ -10,7 +10,6 @@ use App\Models\Information;
use App\Models\Invoice;
use App\Models\Label;
use App\Models\Package;
use App\Models\Project;
use App\Models\Site;
use App\Models\Ticket;
use App\Models\Ticket\TicketType;
@ -88,14 +87,12 @@ class RouteServiceProvider extends ServiceProvider
});
Route::model('brand', Brand::class);
Route::model('label', Label::class);
Route::model('marker', Marker::class);
Route::model('package', Package::class);
Route::model('information', Information::class);
Route::model('company', Company::class);
Route::model('invoice', Invoice::class);
Route::model('user', User::class);
Route::model('site', Site::class);
Route::model('project', Project::class);
Route::model('ticket_type', TicketType::class);
Route::bind('ticket', function ($value) {
return Ticket::where('id', $value)->has('site')->firstOrFail();

View file

@ -8,21 +8,29 @@ return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->foreignId('favourite_site_id')->nullable()->after('gitlab_id')->constrained('sites')->onDelete('set null');
$table->foreignId('favourite_site_id')
->nullable()
->after('gitlab_id')
->constrained('sites')
->onDelete('set null');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropForeign(['favourite_site_id']);;
$table->dropForeign(['favourite_site_id']);
$table->dropColumn('favourite_site_id');
});
}

View file

@ -0,0 +1,23 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::drop('projects');
}
/**
* Reverse the migrations.
*/
public function down(): void
{
// No revert.
}
};

View file

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('sites', function (Blueprint $table) {
$table->unsignedInteger('gitlab_project_id')
->unique(false)
->nullable()
->change();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('sites', function (Blueprint $table) {
$table->unsignedInteger('gitlab_project_id')
->unique(true)
->nullable()
->change();
});
}
};

View file

@ -4,7 +4,6 @@ return [
'classes' => [
'users' => 'Users',
'accounts' => 'Accounts',
'projects' => 'Projects',
'markers' => 'Markers',
'tickets' => 'Tickets',
'creditlogs' => 'Logs',

View file

@ -4,7 +4,6 @@ return [
'classes' => [
'users' => 'Utilisateurs',
'accounts' => 'Comptes',
'projects' => 'Projets',
'tickets' => 'Tickets',
'creditlogs' => 'Historique',
'sites' => 'Sites',

View file

@ -90,22 +90,4 @@ $modal-content-border-color: rgba($black, 0.1) !default;
// Close
$btn-close-color: $white !default;
$btn-close-opacity: 0.4 !default;
$btn-close-hover-opacity: 1 !default;
// Variables
$enum-activities: (
"0": #fff2cc,
"1": #be8e00,
"2": #a9d08e,
"3": #f00,
"4": #9bc2e6,
);
$enum-project-status: (
"0": #950952,
"1": #5e0035,
"2": #30011c,
"3": #020202,
"4": #023618,
"5": #014941,
"6": #005c69,
);
$btn-close-hover-opacity: 1 !default;

View file

@ -1,46 +0,0 @@
@extends('layout.app', ['brParam' => $projectModel])
@section('title', __('Création d\'un :model', ['model' => Str::singular(__('models.classes.projects'))]))
@section('description', __('Création d\'un tout nouveau :model', ['model' => Str::singular(__('models.classes.projects'))]))
@section('breadcrumb', request()->route()->getName())
@section('content')
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-3">
<div class="d-flex flex-row align-items-center">
<a href="{{ route('bo.projects.index') }}"
class="btn btn-primary text-decoration-none m-0 me-3"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="{{ __('crud.helpers.list_all', ['model' => __('models.classes.projects')]) }}">
<i class="fa-solid fa-arrow-left"></i>
</a>
@include('breadcrumbs.breadcrumb-body', ['brParam' => $projectModel])
</div>
<div class="mb-2 mb-md-0">
<button id="formSubmitClone"
type="submit"
class="btn btn-primary"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="{{ __('crud.helpers.save_model', ['model' => Str::singular(__('models.classes.projects'))]) }}">
{{ __('crud.actions.save') }}
</button>
</div>
</div>
<form action="{{ route('bo.projects.store') }}" method="POST" enctype="multipart/form-data">
@csrf
@include('bo.pages.projects.form-inputs')
<div class="row mt-3">
<div class="col text-center">
<button id="formSubmit"
type="submit"
class="btn btn-primary"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="{{ __('crud.helpers.save_model', ['model' => Str::singular(__('models.classes.projects'))]) }}">
{{ __('crud.actions.save') }}
</button>
</div>
</div>
</form>
@endsection

View file

@ -1,62 +0,0 @@
@extends('layout.app', ['brParam' => $projectModel])
@section('title', __('Édition d\'un :model', ['model' => Str::singular(__('models.classes.projects'))]))
@section('description', __('Édition d\'un :model déjà existant', ['model' => Str::singular(__('models.classes.projects'))]))
@section('breadcrumb', request()->route()->getName())
@section('content')
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-3">
<div class="d-flex flex-row align-items-start">
<a href="{{ route('bo.projects.index') }}"
class="btn btn-primary text-decoration-none m-0 me-2"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="{{ __('crud.helpers.list_all', ['model' => __('models.classes.projects')]) }}">
<i class="fa-solid fa-arrow-left"></i>
</a>
@include('breadcrumbs.breadcrumb-body', ['brParam' => $projectModel])
</div>
<div class="mb-2 mb-md-0">
<form action="{{ route('bo.projects.destroy', $projectModel->id) }}"
method="POST"
class="confirmDeleteTS">
@csrf
@method('DELETE')
<div class="btn-group" role="group">
<button type="submit"
class="btn btn-danger"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="{{ __('crud.helpers.delete_model', ['model' => Str::singular(__('models.classes.projects'))]) }}">
<i class="fa-solid fa-trash"></i>
</button>
<button id="formSubmitClone"
type="submit"
class="btn btn-primary"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="{{ __('crud.helpers.save_model', ['model' => Str::singular(__('models.classes.projects'))]) }}">
{{ __('crud.actions.save') }}
</button>
</div>
</form>
</div>
</div>
<form action="{{ route('bo.projects.update', $projectModel->id) }}" method="POST" enctype="multipart/form-data">
@csrf
@method('PUT')
@include('bo.pages.projects.form-inputs')
<div class="row">
<div class="col text-center">
<button id="formSubmit"
type="submit"
class="btn btn-primary"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="{{ __('crud.helpers.save_model', ['model' => Str::singular(__('models.classes.projects'))]) }}">
{{ __('crud.actions.save') }}
</button>
</div>
</div>
</form>
@endsection

View file

@ -1,75 +0,0 @@
<div class="row">
{{-- NAME --}}
<div class="col-12 col-md-6 mb-3">
<fieldset class="border h-100 bg-white p-3">
<legend>{{ Str::ucfirst(__('validation.attributes.name')) }}</legend>
<div class="row">
<div class="col-12">
@include('bo.partials.inputs.varchar', [
'id' => 'projectName',
'label' => __(':field du :model', [
'field' => Str::ucfirst(__('validation.attributes.name')),
'model' => Str::singular(__('models.classes.projects'))
]),
'hasWordCounter' => true,
'name' => 'name',
'required' => true,
'model' => $projectModel ?? null,
'helper' => __('validation.between.string', [
'attribute' => __(':field du :model', [
'field' => Str::ucfirst(__('validation.attributes.name')),
'model' => Str::singular(__('models.classes.projects'))
]),
'min' => 3,
'max' => 255
]),
'placeholder' => __(':field du :model', [
'field' => Str::ucfirst(__('validation.attributes.name')),
'model' => Str::singular(__('models.classes.projects'))
]),
])
</div>
</div>
</fieldset>
</div>
{{-- SITE_ID --}}
<div class="col-12 col-md-6 pb-3">
<fieldset class="border bg-white h-100 p-3">
<legend>{{ Str::singular(trans('models.classes.sites')) }}</legend>
<div class="row">
<div class="col-12">
@include('bo.partials.inputs.search-belongs-to-dropdown', [
'id' => 'projectSite',
'label' => __(':model associé', ['model' => Str::singular(trans('models.classes.sites'))]),
'name' => 'site_id',
'targetModel' => 'App\Models\Site',
'routeName' => 'projects.json-paginate',
'modelRelated' => $siteModels ?? [],
'modelSelected' => $projectModel->site ?? [],
'readonly' => false,
'placeholder' => __('Rechercher une :model', ['model' => Str::singular(trans('models.classes.sites'))]),
'helper' => __('Sélectionner un :model qui sera associé parmi la liste (un seul et unique :model doit être choisi).', [
'model' => Str::singular(__('models.classes.sites'))
]),
'required' => true,
])
</div>
</div>
</fieldset>
</div>
</div>
@push('scripts')
<script nonce="{{ $nonce }}">
document.addEventListener("DOMContentLoaded", () => {
// Submit button clone.
const submit = document.getElementById('formSubmit')
submitClone = document.getElementById('formSubmitClone');
submitClone.addEventListener('click', (event) => {
event.preventDefault();
submit.click('');
})
});
</script>
@endpush

View file

@ -1,77 +0,0 @@
@extends('layout.app')
@section('title', __('models.classes.projects'))
@section('description', __('Liste de tous les :model', ['model' => __('models.classes.projects')]))
@section('content')
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center">
@include('breadcrumbs.breadcrumb-body')
<a href="{{ route('bo.projects.create') }}"
class="btn btn-primary float-right"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="{{ __('crud.helpers.create_model', ['model' => Str::singular(__('models.classes.projects'))]) }}">
<i class="fa-solid fa-plus"></i>
</a>
</div>
@include('bo.modules.search-bar')
<table class="table table-hover">
@if (count($projectModels) > 0)
<thead>
@php
$cols = [
'name' => Str::ucfirst(__('validation.attributes.name')),
'site_id' => Str::ucfirst(__('models.classes.sites')),
];
@endphp
@include('bo.modules.table-col-sorter', [
'cols' => $cols,
'mobileHide' => [],
])
</thead>
<tbody>
@foreach ($projectModels as $projectModel)
<tr class="list-item border-bottom">
<td class="text-center align-middle">{{ $projectModel->name }}</td>
<td class="text-center align-middle">{{ $projectModel->site->name }}</td>
<td class="text-end align-middle">
<form action="{{ route('bo.projects.destroy', $projectModel->id) }}"
method="POST"
class="btn-group confirmDeleteTS"
novalidate>
<a class="btn btn-sm btn-info"
href="{{ route('bo.projects.show', ['project' => $projectModel->id]) }}"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="{{ __('crud.actions.show') }}">
<i class="fa-solid fa-eye"></i>
</a>
<a class="btn btn-sm btn-primary"
href="{{ route('bo.projects.edit', ['project' => $projectModel->id]) }}"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="{{ __('crud.actions.update') }}">
<i class="fa-solid fa-pencil"></i>
</a>
@csrf
@method('DELETE')
<button type="submit"
class="btn btn-sm btn-danger"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="{{ __('crud.actions.delete') }}">
<i class="fa-solid fa-trash"></i>
</button>
</form>
</td>
</tr>
@endforeach
</tbody>
@else
<tr>
<td class="text-center fw-bold border-0">{{ __('crud.other.no_model_found', ['model' => __('models.classes.projects')]) }}</td>
</tr>
@endif
</table>
{!! $projectModels->links() !!}
@endsection

View file

@ -1,40 +0,0 @@
@extends('layout.app', ['brParam' => $projectModel])
@section('title', __('Aperçu d\'un :model', ['model' => Str::singular(__('models.classes.projects'))]))
@section('description', __('Aperçu d\'un :model déjà existant', ['model' => Str::singular(__('models.classes.projects'))]))
@section('breadcrumb', request()->route()->getName())
@section('content')
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-3">
<div class="d-flex flex-row align-items-start">
<a href="{{ route('bo.projects.index') }}"
class="btn btn-primary text-decoration-none m-0 me-2"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="{{ __('crud.helpers.list_all', ['model' => __('models.classes.projects')]) }}">
<i class="fa-solid fa-arrow-left"></i>
</a>
@include('breadcrumbs.breadcrumb-body', ['brParam' => $projectModel])
</div>
</div>
<table class="table table-hover table-borderless">
<tbody>
<tr class="list-item border-bottom">
<td class="text-center align-middle">{{ Str::ucfirst(__('validation.attributes.name')) }}</td>
<td class="text-center align-middle">{{ $projectModel->name }}</td>
</tr>
<tr class="list-item border-bottom">
<td class="text-center align-middle">{{ Str::ucfirst(__('validation.attributes.sites')) }}</td>
<td class="text-center align-middle">{{ $projectModel->site->name }}</td>
</tr>
<tr class="list-item border-bottom">
<td class="text-center align-middle">{{ Str::ucfirst(__('validation.attributes.created_at')) }}</td>
<td class="text-center align-middle"><span class="badge bg-info">{{ $projectModel->created_at }}</span></td>
</tr>
<tr class="list-item">
<td class="text-center align-middle">{{ Str::ucfirst(__('validation.attributes.updated_at')) }}</td>
<td class="text-center align-middle"><span class="badge bg-info">{{ $projectModel->updated_at }}</span></td>
</tr>
</tbody>
</table>
@endsection

View file

@ -26,7 +26,7 @@
@endforeach
</select>
</div>
@can('changeStatus', App\Model\Ticket::class)
@can('changeStatus', App\Models\Ticket::class)
<div class="form-group col">
<label class="col-form-label" for="status">{{ __('Status') }}</label>
<select class="form-select" id="status" name="status">
@ -75,7 +75,7 @@
<input name="status" type="hidden" value="{{ \App\Enums\Tickets\TicketStatusEnum::new }}" required>
@endcan
@can('assignLabels', App\Model\Ticket::class)
@can('assignLabels', App\Models\Ticket::class)
<div class="form-group col">
<label class="col-form-label" for="labels">{{ __('Étiquettes') }}</label>
<div class="row">
@ -127,7 +127,7 @@
<hr>
@endfor
@can('changeInternalStatus', App\Model\Ticket::class)
@can('changeInternalStatus', App\Models\Ticket::class)
<div class="form-group text-center">
<input id="is_internal" name="is_internal" type="checkbox" @if (old('is_internal', isset($ticket) and $ticket->is_internal)) checked @endif>
<label class="col-form-label" for="is_internal">Ticket interne</label>

View file

@ -28,9 +28,6 @@
<a class="dropdown-item @if(Route::is('bo.sites.*'))active @endif" href="{{ route('bo.sites.index') }}">{{ __('models.classes.sites') }}</a>
@endcan
@can('isAdmin')
<a class="dropdown-item @if(Route::is('bo.projects.*'))active @endif" href="{{ route('bo.projects.index') }}">{{ __('models.classes.projects') }}</a>
@endcan
@can('isAdmin')
<a class="dropdown-item @if(Route::is('bo.invoices.*'))active @endif" href="{{ route('bo.invoices.index', ['status' => \App\Enums\Invoices\InvoiceStatusEnum::opened]) }}">{{ __('models.classes.invoices') }}</a>
@endcan
@can('isAdmin')

View file

@ -20,11 +20,6 @@
show: "{{ route('bo.sites.show', ['site' => 'SITE']) }}",
"json-paginate": "{{ route('bo.sites.json-paginate') }}",
};
window.__SYSTEM._routes.projects = {
index: "{{ route('bo.projects.index') }}",
show: "{{ route('bo.projects.show', ['project' => 'PROJECT']) }}",
"json-paginate": "{{ route('bo.projects.json-paginate') }}"
};
window.__SYSTEM._routes.invoices = {
"json-paginate": "{{ route('bo.invoices.json-paginate') }}",
};

View file

@ -7,7 +7,6 @@ use App\Models\Information;
use App\Models\Invoice;
use App\Models\Label;
use App\Models\Package;
use App\Models\Project;
use App\Models\Site;
use App\Models\Ticket\TicketType;
use App\Models\User;
@ -113,23 +112,6 @@ Breadcrumbs::for('bo.sites.show', function (Generator $trail, Site $site) {
$trail->push(__('crud.actions.show'), route('bo.sites.show', $site->id));
});
// * PROJECTS
Breadcrumbs::for('bo.projects.index', function (Generator $trail) {
$trail->push(__('models.classes.projects'), route('bo.projects.index'));
});
Breadcrumbs::for('bo.projects.create', function (Generator $trail) {
$trail->parent('bo.projects.index');
$trail->push(trans('crud.actions.create'), route('bo.projects.create'));
});
Breadcrumbs::for('bo.projects.edit', function (Generator $trail, Project $project) {
$trail->parent('bo.projects.index');
$trail->push(trans('crud.actions.edit'), route('bo.projects.edit', $project->id));
});
Breadcrumbs::for('bo.projects.show', function (Generator $trail, Project $project) {
$trail->parent('bo.projects.index');
$trail->push(trans('crud.actions.show'), route('bo.projects.show', $project->id));
});
// * BRANDS
Breadcrumbs::for('bo.brands.index', function (Generator $trail) {
$trail->push(__('models.classes.brands'), route('bo.brands.index'));

View file

@ -9,7 +9,6 @@ use App\Http\Controllers\Bo\InformationController;
use App\Http\Controllers\Bo\InvoiceController;
use App\Http\Controllers\Bo\LabelController;
use App\Http\Controllers\Bo\PackageController;
use App\Http\Controllers\Bo\ProjectController;
use App\Http\Controllers\Bo\SiteController;
use App\Http\Controllers\Bo\TicketController;
use App\Http\Controllers\Bo\TicketTypeController;
@ -75,11 +74,6 @@ Route::middleware('admin-secure')
Route::get('/invoices/{invoice}/download', [InvoiceController::class, 'download'])
->name('invoices.download');
// * PROJECTS
Route::resource('projects', ProjectController::class);
Route::get('/projects/json/paginate', [ProjectController::class, 'jsonPaginate'])
->name('projects.json-paginate');
// * SITES
Route::resource('sites', SiteController::class)->except('index');
Route::get('/sites/json/paginate', [SiteController::class, 'jsonPaginate'])
@ -158,7 +152,10 @@ Route::middleware('admin-secure')
Route::post('/{ticket}/link', [TicketController::class, 'linkTicket'])
->middleware('can:linkTicket,ticket')
->name('link');
Route::delete('/{ticket}/unlink/{linkedTicket}', [TicketController::class, 'unlinkTicket'])
Route::delete(
'/{ticket}/unlink/{linkedTicket}',
[TicketController::class, 'unlinkTicket']
)
->middleware('can:linkTicket,ticket')
->name('unlink');
Route::put('/{ticket}/archive', [TicketController::class, 'setToArchive'])