feat: ✨ add sites crud #120
This commit is contained in:
parent
833594afdf
commit
397eeeed77
21 changed files with 2079 additions and 10 deletions
295
app/Http/Controllers/Back/SiteController.php
Normal file
295
app/Http/Controllers/Back/SiteController.php
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Back;
|
||||
|
||||
use App\Http\Requests\Back\Sites\StoreSiteRequest;
|
||||
use App\Http\Requests\Back\Sites\UpdateSiteRequest;
|
||||
use App\Models\GitlabProject;
|
||||
use App\Models\Site;
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use Uwil\LaravelBackend\Controllers\BackendController;
|
||||
use Uwil\LaravelBackend\Resources\CommonResource;
|
||||
use Uwil\LaravelBackend\Support\UserRoleRegistry;
|
||||
|
||||
/**
|
||||
* Handles CRUD operations for Site in the backend.
|
||||
*/
|
||||
class SiteController extends BackendController
|
||||
{
|
||||
/**
|
||||
* Project managers collection.
|
||||
*
|
||||
* @var \Illuminate\Support\Collection<int,\Uwil\LaravelBackend\Models\User>
|
||||
*/
|
||||
private \Illuminate\Support\Collection $userModelsProjectManager;
|
||||
|
||||
/**
|
||||
* Project managers collection.
|
||||
*
|
||||
* @var \Illuminate\Support\Collection<int,\Uwil\LaravelBackend\Models\User>
|
||||
*/
|
||||
private \Illuminate\Support\Collection $userModelsClient;
|
||||
|
||||
/**
|
||||
* Gitlab projects collection.
|
||||
*
|
||||
* @var \Illuminate\Support\Collection<int,\App\Models\GitlabProject>
|
||||
*/
|
||||
private \Illuminate\Support\Collection $gitlabProjectModels;
|
||||
|
||||
/**
|
||||
* Create the controller instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->authorizeResource(Site::class);
|
||||
$this->modelName = Site::modelName();
|
||||
$this->modelPage = Site::modelPage();
|
||||
$userModels = User::getModelsCached()
|
||||
->where("published", true)
|
||||
->where('deleted_at', null)
|
||||
->values();
|
||||
$this->userModelsProjectManager = $userModels
|
||||
->filter(fn(User $user) => $user->role->value === UserRoleRegistry::get('project-manager')->value)
|
||||
->values();
|
||||
$this->userModelsClient = $userModels
|
||||
->filter(fn(User $user) => $user->role->value === UserRoleRegistry::get('client')->value)
|
||||
->values();
|
||||
$this->gitlabProjectModels = GitlabProject::getModelsCached();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Inertia\Response
|
||||
*/
|
||||
public function index(Request $request): \Inertia\Response
|
||||
{
|
||||
/** @var \Illuminate\Database\Eloquent\Builder<\App\Models\Site> $query */
|
||||
$query = Site::query()->with(['projectManager', 'gitlabProject']);
|
||||
;
|
||||
|
||||
if ($request->routeIs("back.$this->modelName.trash")) {
|
||||
$query = $query->onlyTrashed();
|
||||
}
|
||||
|
||||
/** @var string $search Search field */
|
||||
$search = $request->search;
|
||||
if ($search) {
|
||||
$this->searchQuery(
|
||||
$query,
|
||||
$search,
|
||||
null,
|
||||
'name',
|
||||
);
|
||||
}
|
||||
$searchFields = \implode(', ', [
|
||||
trans('validation.attributes.name'),
|
||||
]);
|
||||
|
||||
/** Sort columns with a query */
|
||||
$this->sortQuery($query);
|
||||
|
||||
/** Custom pagination */
|
||||
$models = $this->paginate($query);
|
||||
|
||||
return $this->inertiaRender("back/Pages/$this->modelPage/Index", [
|
||||
'models' => CommonResource::collection($models),
|
||||
'search' => $search,
|
||||
'searchFields' => $searchFields,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*
|
||||
* @param \App\Models\Site $site
|
||||
* @return \Inertia\Response
|
||||
*/
|
||||
public function create(Site $site): \Inertia\Response
|
||||
{
|
||||
return $this->inertiaRender("back/Pages/$this->modelPage/Create", [
|
||||
'data' => CommonResource::make($site),
|
||||
'userModelsProjectManager' => $this->userModelsProjectManager,
|
||||
'userModelsClient' => $this->userModelsClient,
|
||||
'gitlabProjectModels' => $this->gitlabProjectModels,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a specific resource.
|
||||
*
|
||||
* @param \App\Models\Site $site
|
||||
* @return \Inertia\Response
|
||||
*/
|
||||
public function show(Site $site): \Inertia\Response
|
||||
{
|
||||
return $this->inertiaRender("back/Pages/$this->modelPage/Show", [
|
||||
'data' => CommonResource::make($site->load(['projectManager', 'gitlabProject', 'users'])),
|
||||
'userModelsProjectManager' => $this->userModelsProjectManager,
|
||||
'userModelsClient' => $this->userModelsClient,
|
||||
'gitlabProjectModels' => $this->gitlabProjectModels,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource.
|
||||
*
|
||||
* @param \App\Http\Requests\Back\Sites\StoreSiteRequest $request
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function store(StoreSiteRequest $request): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
return DB::transaction(function () use ($request) {
|
||||
$validatedData = $request->validated();
|
||||
$site = new Site();
|
||||
$site->fill(Arr::except($validatedData, 'users'));
|
||||
|
||||
if ($site->saveOrFail()) {
|
||||
$site->users()->syncWithoutDetaching(Arr::get($validatedData, 'users'));
|
||||
return redirect()->route("back.$this->modelName.edit", $site)
|
||||
->with('success', trans('uwil-backend::crud.messages.has_been_created', [
|
||||
'model' => Str::ucfirst(trans_choice("models.classes.$this->modelName", 1)),
|
||||
]));
|
||||
}
|
||||
return redirect()->back()
|
||||
->with('error', trans('uwil-backend::crud.messages.cannot_be_created', [
|
||||
'model' => Str::ucfirst(trans_choice("models.classes.$this->modelName", 1)),
|
||||
]));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*
|
||||
* @param \App\Models\Site $site
|
||||
* @return \Inertia\Response
|
||||
*/
|
||||
public function edit(Site $site): \Inertia\Response
|
||||
{
|
||||
return $this->inertiaRender("back/Pages/$this->modelPage/Edit", [
|
||||
'data' => CommonResource::make($site->load(['projectManager', 'gitlabProject', 'users'])),
|
||||
'userModelsProjectManager' => $this->userModelsProjectManager,
|
||||
'userModelsClient' => $this->userModelsClient,
|
||||
'gitlabProjectModels' => $this->gitlabProjectModels,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource.
|
||||
*
|
||||
* @param \App\Http\Requests\Back\Sites\UpdateSiteRequest $request
|
||||
* @param \App\Models\Site $site
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function update(
|
||||
UpdateSiteRequest $request,
|
||||
Site $site
|
||||
): \Illuminate\Http\RedirectResponse {
|
||||
return DB::transaction(function () use ($request, $site) {
|
||||
$validatedData = $request->validated();
|
||||
$site->fill(Arr::except($validatedData, 'users'));
|
||||
|
||||
if ($site->saveOrFail()) {
|
||||
$site->users()->sync(Arr::get($validatedData, 'users'));
|
||||
return redirect()->route("back.$this->modelName.edit", $site)
|
||||
->with('success', trans('uwil-backend::crud.messages.has_been_updated', [
|
||||
'model' => Str::ucfirst(trans_choice("models.classes.$this->modelName", 1)),
|
||||
]));
|
||||
}
|
||||
return redirect()->route("back.$this->modelName.edit", $site)
|
||||
->with('error', trans('uwil-backend::crud.messages.cannot_be_updated', [
|
||||
'model' => Str::ucfirst(trans_choice("models.classes.$this->modelName", 1)),
|
||||
]));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicate the specified resource.
|
||||
*
|
||||
* @param \App\Models\Site $site
|
||||
* @return \Inertia\Response
|
||||
*/
|
||||
public function duplicate(Site $site): \Inertia\Response
|
||||
{
|
||||
return $this->create($site->replicate());
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the specified resource.
|
||||
*
|
||||
* @param integer $site
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function restore(int $site): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$site = Site::withTrashed()->findOrFail($site);
|
||||
/** @var array<string,string> */
|
||||
$previousQueries = [];
|
||||
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
|
||||
if ($site->restore()) {
|
||||
return redirect()->route("back.$this->modelName.trash", $previousQueries)
|
||||
->with('success', trans('uwil-backend::crud.messages.has_been_restored', [
|
||||
'model' => Str::ucfirst(trans_choice("models.classes.$this->modelName", 1)),
|
||||
]));
|
||||
}
|
||||
return redirect()->back()
|
||||
->with('error', trans('uwil-backend::crud.messages.cannot_be_restored', [
|
||||
'model' => Str::ucfirst(trans_choice("models.classes.$this->modelName", 1)),
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the specified resource.
|
||||
*
|
||||
* @param \App\Models\Site $site
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function destroy(Site $site): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
/** @var array<string,string> */
|
||||
$previousQueries = [];
|
||||
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
|
||||
if ($site->delete()) {
|
||||
return redirect()->route("back.$this->modelName.index", $previousQueries)
|
||||
->with('success', trans('uwil-backend::crud.messages.has_been_deleted', [
|
||||
'model' => Str::ucfirst(trans_choice("models.classes.$this->modelName", 1)),
|
||||
]));
|
||||
}
|
||||
return redirect()->back()
|
||||
->with('error', trans('uwil-backend::crud.messages.cannot_be_deleted', [
|
||||
'model' => Str::ucfirst(trans_choice("models.classes.$this->modelName", 1)),
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the specified resource.
|
||||
*
|
||||
* @param \App\Models\Site|integer $site
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function forceDestroy(Site|int $site): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
if (is_int($site)) {
|
||||
$site = Site::withTrashed()->findOrFail($site);
|
||||
}
|
||||
/** @var array<string,string> */
|
||||
$previousQueries = [];
|
||||
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
|
||||
if ($site->forceDelete()) {
|
||||
return redirect()->route("back.$this->modelName.trash", $previousQueries)
|
||||
->with('success', trans('uwil-backend::crud.messages.has_been_deleted', [
|
||||
'model' => Str::ucfirst(trans_choice("models.classes.$this->modelName", 1)),
|
||||
]));
|
||||
}
|
||||
return redirect()->back()
|
||||
->with('error', trans('uwil-backend::crud.messages.cannot_be_deleted', [
|
||||
'model' => Str::ucfirst(trans_choice("models.classes.$this->modelName", 1)),
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ use App\Models\GitlabProject;
|
|||
use App\Models\InvoiceCompany;
|
||||
use App\Models\InvoicePackage;
|
||||
use App\Models\Notice;
|
||||
use App\Models\Site;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Inertia\Middleware;
|
||||
|
|
@ -47,6 +48,11 @@ class HandleInertiaRequests extends Middleware
|
|||
'viewTrash' => Gate::allows('viewTrash', GitlabProject::class),
|
||||
'create' => Gate::allows('create', GitlabProject::class),
|
||||
],
|
||||
'sites' => [
|
||||
'viewAny' => Gate::allows('viewAny', Site::class),
|
||||
'viewTrash' => Gate::allows('viewTrash', Site::class),
|
||||
'create' => Gate::allows('create', Site::class),
|
||||
],
|
||||
],
|
||||
],
|
||||
'noticeTypeEnum' => NoticeTypeEnum::toArray(),
|
||||
|
|
|
|||
76
app/Http/Requests/Back/Sites/StoreSiteRequest.php
Normal file
76
app/Http/Requests/Back/Sites/StoreSiteRequest.php
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Back\Sites;
|
||||
|
||||
use App\Models\Site;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
/**
|
||||
* Handles authorization and validation for creating a new Site.
|
||||
*/
|
||||
class StoreSiteRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return Gate::check('create', Site::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
$this->merge([
|
||||
'project_manager_id' => is_array($this->project_manager_id)
|
||||
? intval($this->project_manager_id[0]) ?? null
|
||||
: $this->project_manager_id,
|
||||
'gitlab_project_id' => is_array($this->gitlab_project_id)
|
||||
? intval($this->gitlab_project_id[0]) ?? null
|
||||
: $this->gitlab_project_id,
|
||||
'users' => is_array($this->users) ? array_map('intval', $this->users) : $this->users,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'required|string|between:3,255|unique:sites,name',
|
||||
'url_re7' => 'nullable|string|url:http,https|unique:sites,url_re7',
|
||||
'url_prod' => 'nullable|string|url:http,https|unique:sites,url_prod',
|
||||
'project_manager_id' => 'required|integer|exists:users,id',
|
||||
'gitlab_project_id' => 'required|integer|exists:gitlab_projects,id|unique:sites,gitlab_project_id',
|
||||
'users' => 'nullable|array',
|
||||
'users.*' => 'integer|exists:users,id|distinct',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attributes for validator errors.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'name' => trans('validation.custom.name'),
|
||||
'url_re7' => trans('validation.custom.url_re7'),
|
||||
'url_prod' => trans('validation.custom.url_prod'),
|
||||
'project_manager_id' => trans('validation.custom.project_manager_id'),
|
||||
'gitlab_project_id' => trans_choice('models.classes.gitlab-projects', 1),
|
||||
'users' => trans_choice('uwil-backend::models.classes.users', \INF),
|
||||
];
|
||||
}
|
||||
}
|
||||
61
app/Http/Requests/Back/Sites/UpdateSiteRequest.php
Normal file
61
app/Http/Requests/Back/Sites/UpdateSiteRequest.php
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Back\Sites;
|
||||
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
/**
|
||||
* Handles authorization and validation for updating an existing Site.
|
||||
*/
|
||||
class UpdateSiteRequest extends StoreSiteRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
#[\Override]
|
||||
public function authorize(): bool
|
||||
{
|
||||
return Gate::check('update', $this->route('site'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
#[\Override]
|
||||
public function rules(): array
|
||||
{
|
||||
/** @var \App\Models\Site $site */
|
||||
$site = $this->route('site');
|
||||
return array_merge(parent::rules(), [
|
||||
'name' => [
|
||||
'required',
|
||||
'string',
|
||||
'between:3,255',
|
||||
Rule::unique('sites', 'name')->ignore($site->getRouteKey()),
|
||||
],
|
||||
'url_re7' => [
|
||||
'required',
|
||||
'string',
|
||||
'url:http,https',
|
||||
Rule::unique('sites', 'url_re7')->ignore($site->getRouteKey()),
|
||||
],
|
||||
'url_prod' => [
|
||||
'required',
|
||||
'string',
|
||||
'url:http,https',
|
||||
Rule::unique('sites', 'url_prod')->ignore($site->getRouteKey()),
|
||||
],
|
||||
'gitlab_project_id' => [
|
||||
'required',
|
||||
'integer',
|
||||
'exists:gitlab_projects,id',
|
||||
Rule::unique('sites', 'gitlab_project_id')->ignore($site->getRouteKey()),
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
124
app/Models/Site.php
Normal file
124
app/Models/Site.php
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Uwil\LaravelActivityLogs\Traits\HasActivityLog;
|
||||
use Uwil\LaravelBackend\Traits\Models\HasCache;
|
||||
use Uwil\LaravelBackend\Traits\Models\HasModelNaming;
|
||||
use Uwil\LaravelCommon\Helpers\ToolboxHelper;
|
||||
use Uwil\LaravelI18n\Traits\Models\HasTranslations;
|
||||
|
||||
/**
|
||||
* @property integer $id Id of the site.
|
||||
* @property string $name Name of the site.
|
||||
* @property string $url_re7 Re7 url of the site.
|
||||
* @property string $url_prod Prod url of the site.
|
||||
* @property \App\Models\User $project_manager_id Project manager associated.
|
||||
* @property-read \Illuminate\Support\Carbon $created_at Date the site was created.
|
||||
* @property-read \Illuminate\Support\Carbon $updated_at Date the site was updated.
|
||||
* @property-read \Illuminate\Support\Carbon $deleted_at Date the site was deleted.
|
||||
*/
|
||||
class Site extends Model
|
||||
{
|
||||
use HasActivityLog;
|
||||
use HasCache;
|
||||
/** @use \Illuminate\Database\Eloquent\Factories\HasFactory<\Database\Factories\SiteFactory> */
|
||||
use HasFactory;
|
||||
use HasModelNaming;
|
||||
use HasTranslations;
|
||||
use SoftDeletes;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'url_re7',
|
||||
'url_prod',
|
||||
'project_manager_id',
|
||||
'gitlab_project_id',
|
||||
];
|
||||
|
||||
/**
|
||||
* The translatable columns.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
public $translatable = [
|
||||
'name',
|
||||
];
|
||||
|
||||
/**
|
||||
* Relations should be included in the cache.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected static $cacheRelations = [
|
||||
'projectManager',
|
||||
'gitlabProject',
|
||||
];
|
||||
|
||||
/**
|
||||
* Perform any actions required after the model boots.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
#[\Override]
|
||||
protected static function booted(): void
|
||||
{
|
||||
$assertUrls = function (self $model) {
|
||||
if ($model->url_re7) {
|
||||
ToolboxHelper::assertNoInternalUrls('url_re7', $model->url_re7);
|
||||
}
|
||||
if ($model->url_prod) {
|
||||
ToolboxHelper::assertNoInternalUrls('url_prod', $model->url_prod);
|
||||
}
|
||||
};
|
||||
|
||||
static::creating($assertUrls);
|
||||
static::updating($assertUrls);
|
||||
}
|
||||
|
||||
// * RELATIONS.
|
||||
|
||||
/**
|
||||
* Get users associated to the site.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\MorphToMany<\App\Models\User>
|
||||
*/
|
||||
public function users(): \Illuminate\Database\Eloquent\Relations\MorphToMany
|
||||
{
|
||||
return $this->morphedByMany(
|
||||
User::class,
|
||||
'sitable',
|
||||
'sitables',
|
||||
'site_id',
|
||||
'sitable_id'
|
||||
)->withPivot('created_at');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get project manager of the site.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\App\Models\User,\App\Models\Site>
|
||||
*/
|
||||
public function projectManager(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'project_manager_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get project manager of the site.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<\App\Models\GitlabProject,\App\Models\Site>
|
||||
*/
|
||||
public function gitlabProject(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(GitlabProject::class, 'gitlab_project_id');
|
||||
}
|
||||
}
|
||||
112
app/Policies/SitePolicy.php
Normal file
112
app/Policies/SitePolicy.php
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Uwil\LaravelBackend\Policies\Rules\UserStaticRules;
|
||||
use Uwil\LaravelBackend\Support\UserRoleRegistry;
|
||||
|
||||
/**
|
||||
* Handles authorization for Site model actions.
|
||||
*/
|
||||
class SitePolicy
|
||||
{
|
||||
/**
|
||||
* Determine whether the user can view any models.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $authModel
|
||||
* @return boolean
|
||||
*/
|
||||
public function viewAny(Model $authModel): bool
|
||||
{
|
||||
return UserStaticRules::atLeastRole($authModel, UserRoleRegistry::get('client')->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can view trash models.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $authModel
|
||||
* @return boolean
|
||||
*/
|
||||
public function viewTrash(Model $authModel): bool
|
||||
{
|
||||
return UserStaticRules::atLeastRole($authModel, UserRoleRegistry::get('developper')->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can view the model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $authModel
|
||||
* @return boolean
|
||||
*/
|
||||
public function view(Model $authModel): bool
|
||||
{
|
||||
return UserStaticRules::atLeastRole($authModel, UserRoleRegistry::get('client')->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can create models.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $authModel
|
||||
* @return boolean
|
||||
*/
|
||||
public function create(Model $authModel): bool
|
||||
{
|
||||
return UserStaticRules::atLeastRole($authModel, UserRoleRegistry::get('developper')->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can update the model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $authModel
|
||||
* @return boolean
|
||||
*/
|
||||
public function update(Model $authModel): bool
|
||||
{
|
||||
return UserStaticRules::atLeastRole($authModel, UserRoleRegistry::get('developper')->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can duplicate the model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $authModel
|
||||
* @return boolean
|
||||
*/
|
||||
public function duplicate(Model $authModel): bool
|
||||
{
|
||||
return UserStaticRules::atLeastRole($authModel, UserRoleRegistry::get('developper')->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can restore the model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $authModel
|
||||
* @return boolean
|
||||
*/
|
||||
public function restore(Model $authModel): bool
|
||||
{
|
||||
return UserStaticRules::atLeastRole($authModel, UserRoleRegistry::get('developper')->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can soft delete the model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $authModel
|
||||
* @return boolean
|
||||
*/
|
||||
public function delete(Model $authModel): bool
|
||||
{
|
||||
return UserStaticRules::atLeastRole($authModel, UserRoleRegistry::get('developper')->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can permanently delete the model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $authModel
|
||||
* @return boolean
|
||||
*/
|
||||
public function forceDelete(Model $authModel): bool
|
||||
{
|
||||
return UserStaticRules::atLeastRole($authModel, UserRoleRegistry::get('developper')->value);
|
||||
}
|
||||
}
|
||||
49
database/factories/SiteFactory.php
Normal file
49
database/factories/SiteFactory.php
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\GitlabProject;
|
||||
use App\Models\Site;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Uwil\LaravelBackend\Support\UserRoleRegistry;
|
||||
|
||||
/**
|
||||
* Factory for generating Site model instances with randomized data.
|
||||
*/
|
||||
class SiteFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* Associated model.
|
||||
*
|
||||
* @var class-string<\App\Models\Site>
|
||||
*/
|
||||
protected $model = Site::class;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
// Get random project manager or create one.
|
||||
$enumValue = UserRoleRegistry::get('project-manager')->value;
|
||||
$projectManager = User::getModelsCached()
|
||||
->where('published', true)
|
||||
->where('role', $enumValue)
|
||||
->shuffle()
|
||||
->first() ?? User::factory()->create([
|
||||
'published' => true,
|
||||
'role' => $enumValue,
|
||||
]);
|
||||
|
||||
return [
|
||||
"name" => fake()->unique()->words(3, true),
|
||||
"url_re7" => fake()->boolean(75) ? fake()->unique()->url() : null,
|
||||
"url_prod" => fake()->boolean(75) ? fake()->unique()->url() : null,
|
||||
"project_manager_id" => fake()->boolean(75) ? $projectManager->getKey() : null,
|
||||
"gitlab_project_id" => fake()->unique()->randomElement(GitlabProject::pluck("id")),
|
||||
];
|
||||
}
|
||||
}
|
||||
40
database/migrations/2026_04_30_160235_create_sites_table.php
Normal file
40
database/migrations/2026_04_30_160235_create_sites_table.php
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('sites', function (Blueprint $table) {
|
||||
$table->id()->comment('Id of the site.');
|
||||
$table->string('name')->unique()->comment('Name of the site.');
|
||||
$table->string('url_re7')->unique()->nullable()->comment('Re7 url of the site.');
|
||||
$table->string('url_prod')->unique()->nullable()->comment('Prod url of the site.');
|
||||
$table->foreignId('project_manager_id')->nullable()->comment('Project manager associated.')
|
||||
->constrained('users')->onUpdate('restrict')->onDelete('restrict');
|
||||
$table->foreignId('gitlab_project_id')->unique()->comment('Gitlab id of the gitlab project.')
|
||||
->constrained('gitlab_projects')->onUpdate('restrict')->onDelete('restrict');
|
||||
$table->timestamp('created_at')->nullable()->comment('Date the site was created.');
|
||||
$table->timestamp('updated_at')->nullable()->comment('Date the site was updated.');
|
||||
$table->timestamp('deleted_at')->nullable()->comment('Date the site was deleted.');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('sites');
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
<?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::create('sitables', function (Blueprint $table) {
|
||||
$table->id()->comment('Primary key of the site relation.');
|
||||
$table->unsignedBigInteger('site_id')->comment('Primary key of the site.');
|
||||
$table->unsignedBigInteger('sitable_id')->comment('Primary key of the associated model.');
|
||||
$table->string('sitable_type')->comment('Model associated.');
|
||||
$table->timestamp('created_at')->nullable()->comment('Date the site relation was created.');
|
||||
|
||||
$table->foreign('site_id')->references('id')->on('sites')->onDelete('cascade');
|
||||
$table->index(['sitable_id', 'sitable_type']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('sitables');
|
||||
}
|
||||
};
|
||||
25
database/seeders/Classic/SiteSeeder.php
Normal file
25
database/seeders/Classic/SiteSeeder.php
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders\Classic;
|
||||
|
||||
use App\Models\Site;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
/**
|
||||
* Seeds the sites table.
|
||||
*/
|
||||
class SiteSeeder extends Seeder
|
||||
{
|
||||
/** @var string $table Associated table to this seeder. */
|
||||
public static string $table = 'sites';
|
||||
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
Site::factory(10)->create();
|
||||
}
|
||||
}
|
||||
|
|
@ -3,5 +3,11 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
"your_field" => "Votre :field",
|
||||
"your_field" => "Votre :field",
|
||||
"model_archived" => ":model est actuellement archivé|:model est actuellement archivée",
|
||||
"access_url" => "Accéder à l'url",
|
||||
"form_title_urls" => "URLs d'accès",
|
||||
"form_subtitle_urls" => "Renseigner les liens de recette et de production liés à ce modèle de données.",
|
||||
"select_element" => "Sélectionner :element",
|
||||
"urls" => "URLs",
|
||||
];
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ return [
|
|||
"notices" => "information|informations",
|
||||
"invoice-packages" => "devis - forfait|devis - forfaits",
|
||||
"invoice-companies" => "devis - société|devis - sociétés",
|
||||
"gitlab-projects" => "Gitlab - projet|Gitlab - projets",
|
||||
"gitlab-projects" => "gitlab - projet|gitlab - projets",
|
||||
"sites" => "site|sites",
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -4,13 +4,18 @@ declare(strict_types=1);
|
|||
|
||||
return [
|
||||
"custom" => [
|
||||
"token" => "token",
|
||||
"new_password" => "nouveau mot de passe",
|
||||
"title" => "titre",
|
||||
"message" => "message",
|
||||
"type" => "type",
|
||||
"name" => "nom",
|
||||
"amount" => "jour",
|
||||
"published" => "publié",
|
||||
"token" => "token",
|
||||
"new_password" => "nouveau mot de passe",
|
||||
"title" => "titre",
|
||||
"message" => "message",
|
||||
"type" => "type",
|
||||
"name" => "nom",
|
||||
"amount" => "jour",
|
||||
"published" => "publié",
|
||||
"project_manager_id" => "chef de projet",
|
||||
"gitlab_id" => "id Gitlab",
|
||||
"url" => "URL",
|
||||
"url_re7" => "URL de recette",
|
||||
"url_prod" => "URL de production",
|
||||
],
|
||||
];
|
||||
73
resources/ts/back/Components/Layout/Sidebar/Links/Sites.tsx
Normal file
73
resources/ts/back/Components/Layout/Sidebar/Links/Sites.tsx
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
// * Packages
|
||||
import { GlobeIcon } from "lucide-react";
|
||||
import { route } from "ziggy-js";
|
||||
import { Link } from "@inertiajs/react";
|
||||
// * Components shadcn
|
||||
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
||||
import { Button } from "@/components/ui/button";
|
||||
// * Other
|
||||
import {
|
||||
filterActions,
|
||||
NavListLinksProps,
|
||||
} from "@/back/Components/Layout/Sidebar/AppSidebar";
|
||||
import { SidebarGroupLink } from "@/back/Components/Layout/Sidebar/SidebarRegistry";
|
||||
import { AuthProp } from "@/back/types/global";
|
||||
import str from "@/hooks/use-string";
|
||||
import { GetTranslation } from "@/lib/utils";
|
||||
import useTrans from "@/hooks/use-translations";
|
||||
import Views from "@/back/lib/actions";
|
||||
|
||||
export default function SidebarLinkSites(auth: AuthProp): SidebarGroupLink {
|
||||
const modelName = "sites";
|
||||
|
||||
// Translations.
|
||||
const transModel = GetTranslation(`models.classes.${modelName}`, {
|
||||
choice: 2,
|
||||
});
|
||||
const transCreate = str(
|
||||
useTrans(`uwil-backend::crud.actions.${Views.CREATION.label}`),
|
||||
)
|
||||
.ucFirst()
|
||||
.value();
|
||||
const transArchives = str(useTrans("uwil-backend::back.archives"))
|
||||
.ucFirst()
|
||||
.value();
|
||||
|
||||
const link: NavListLinksProps = {
|
||||
title: str(transModel).ucFirst().value(),
|
||||
model: modelName,
|
||||
icon: GlobeIcon,
|
||||
show: auth.policies.sites.viewAny,
|
||||
actions: (model: string) =>
|
||||
filterActions({
|
||||
auth,
|
||||
model,
|
||||
items: [
|
||||
auth.policies.sites.create && (
|
||||
<DropdownMenuItem key="create" className="p-0">
|
||||
<Button variant="ghost" asChild>
|
||||
<Link href={route(`back.${model}.create`)} className="w-full">
|
||||
{/* eslint-disable-next-line react/jsx-pascal-case */}
|
||||
<Views.CREATION.icon />
|
||||
{transCreate}
|
||||
</Link>
|
||||
</Button>
|
||||
</DropdownMenuItem>
|
||||
),
|
||||
auth.policies.sites.viewTrash && (
|
||||
<DropdownMenuItem className="p-0">
|
||||
<Button variant="ghost" asChild>
|
||||
<Link href={route(`back.${model}.trash`)} className="w-full">
|
||||
{/* eslint-disable-next-line react/jsx-pascal-case */}
|
||||
<Views.ARCHIVATION.icon />
|
||||
{transArchives}
|
||||
</Link>
|
||||
</Button>
|
||||
</DropdownMenuItem>
|
||||
),
|
||||
],
|
||||
}),
|
||||
};
|
||||
|
||||
return { link, group: "modules" };
|
||||
}
|
||||
63
resources/ts/back/Pages/Sites/Create.tsx
Normal file
63
resources/ts/back/Pages/Sites/Create.tsx
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
// * Packages
|
||||
import { usePage } from "@inertiajs/react";
|
||||
import { route } from "ziggy-js";
|
||||
import { ArrowLeftIcon } from "lucide-react";
|
||||
// * Other
|
||||
import AuthenticatedLayout from "@/back/Layouts/AuthenticatedLayout";
|
||||
import Form from "@/back/Pages/Sites/Form";
|
||||
import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip";
|
||||
import useTrans from "@/hooks/use-translations";
|
||||
import { Actions, Methods } from "@/back/lib/enums";
|
||||
import { AuthProp, DefaultModel, SingleResource } from "@/back/types/global";
|
||||
|
||||
export default function Create({
|
||||
data,
|
||||
modelName,
|
||||
}: {
|
||||
data: SingleResource;
|
||||
modelName: string;
|
||||
}) {
|
||||
const { viewAny } = (usePage().props.auth as AuthProp).policies.sites;
|
||||
const model = data.data as DefaultModel;
|
||||
|
||||
// Translations.
|
||||
const transBackToList = useTrans("uwil-backend::back.crud_back_to_list");
|
||||
const transTitle = useTrans(
|
||||
route().current(`back.${modelName}.duplicate`)
|
||||
? "uwil-backend::crud.helpers.duplication_model"
|
||||
: "uwil-backend::crud.helpers.creation_model",
|
||||
{
|
||||
params: {
|
||||
model: useTrans(`models.classes.${modelName}`, {
|
||||
choice: 1,
|
||||
}),
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const beforeNode = () => {
|
||||
return (
|
||||
viewAny && (
|
||||
<div className="flex justify-end items-center gap-x-1.5">
|
||||
<BtnOptionnalLinkWithTooltip
|
||||
link={route(`back.${modelName}.index`)}
|
||||
content={<ArrowLeftIcon />}
|
||||
tooltipContent={<p>{transBackToList}</p>}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthenticatedLayout title={transTitle} beforeNode={beforeNode()}>
|
||||
<Form
|
||||
model={model}
|
||||
modelName={modelName}
|
||||
modelRoute={modelName}
|
||||
action={Actions.STORE}
|
||||
method={Methods.POST}
|
||||
/>
|
||||
</AuthenticatedLayout>
|
||||
);
|
||||
}
|
||||
71
resources/ts/back/Pages/Sites/Edit.tsx
Normal file
71
resources/ts/back/Pages/Sites/Edit.tsx
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
// * Packages
|
||||
import { usePage } from "@inertiajs/react";
|
||||
import { route } from "ziggy-js";
|
||||
import { ArrowLeftIcon } from "lucide-react";
|
||||
// * Other
|
||||
import AuthenticatedLayout from "@/back/Layouts/AuthenticatedLayout";
|
||||
import ModelActions from "@/back/Components/ModelActions";
|
||||
import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip";
|
||||
import Form from "@/back/Pages/Sites/Form";
|
||||
import useTrans from "@/hooks/use-translations";
|
||||
import { Actions, Methods } from "@/back/lib/enums";
|
||||
import { Views } from "@/back/lib/actions";
|
||||
import { AuthProp, DefaultModel, SingleResource } from "@/back/types/global";
|
||||
|
||||
export default function Edit({
|
||||
data,
|
||||
modelName,
|
||||
}: {
|
||||
data: SingleResource;
|
||||
modelName: string;
|
||||
}) {
|
||||
const { viewAny } = (usePage().props.auth as AuthProp).policies.sites;
|
||||
const model = data.data as DefaultModel;
|
||||
const actionModels = [
|
||||
Views.VISUALIZATION,
|
||||
Views.DUPLICATION,
|
||||
Views.ARCHIVATION,
|
||||
];
|
||||
|
||||
// Translations.
|
||||
const transBackToList = useTrans("uwil-backend::back.crud_back_to_list");
|
||||
const transTitle = useTrans("uwil-backend::crud.helpers.edition_model", {
|
||||
params: {
|
||||
model: useTrans(`models.classes.${modelName}`, {
|
||||
choice: 1,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
const beforeNode = () => {
|
||||
return (
|
||||
viewAny && (
|
||||
<div className="flex justify-end items-center gap-x-1.5">
|
||||
<BtnOptionnalLinkWithTooltip
|
||||
link={route(`back.${modelName}.index`)}
|
||||
content={<ArrowLeftIcon />}
|
||||
tooltipContent={<p>{transBackToList}</p>}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthenticatedLayout title={transTitle} beforeNode={beforeNode()}>
|
||||
<ModelActions
|
||||
actions={actionModels}
|
||||
modelRoute={modelName}
|
||||
model={model}
|
||||
modelUpdatedAt={model.updated_at.toString()}
|
||||
/>
|
||||
<Form
|
||||
model={model}
|
||||
modelName={modelName}
|
||||
modelRoute={modelName}
|
||||
action={Actions.UPDATE}
|
||||
method={Methods.PATCH}
|
||||
/>
|
||||
</AuthenticatedLayout>
|
||||
);
|
||||
}
|
||||
313
resources/ts/back/Pages/Sites/Form.tsx
Normal file
313
resources/ts/back/Pages/Sites/Form.tsx
Normal file
|
|
@ -0,0 +1,313 @@
|
|||
// * Packages
|
||||
import { useForm, usePage } from "@inertiajs/react";
|
||||
import { BookTextIcon, ExternalLinkIcon } from "lucide-react";
|
||||
// * Other
|
||||
import FieldsetWrapper from "@/back/Components/FieldsetWrapper";
|
||||
import useTrans from "@/hooks/use-translations";
|
||||
import InputError from "@/back/Components/InputError";
|
||||
import SubmitButton from "@/back/Components/SubmitButton";
|
||||
import useCrudSubmit from "@/back/hooks/use-crud-submit";
|
||||
import SimpleString from "@/back/Components/Inputs/SimpleString";
|
||||
import {
|
||||
MultiSelect,
|
||||
MultiSelectOption,
|
||||
} from "@/back/Components/Inputs/MultiSelect";
|
||||
import { UserModel } from "@/back/types/global";
|
||||
import str from "@/hooks/use-string";
|
||||
import { GitlabProjectModel, SiteModel } from "@/back/types/project";
|
||||
import { AppEnv } from "@/types/global";
|
||||
import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip";
|
||||
import { GetTranslation, isAValidUrl } from "@/lib/utils";
|
||||
|
||||
export default function Form({
|
||||
model,
|
||||
modelName,
|
||||
modelRoute,
|
||||
action,
|
||||
method,
|
||||
}: {
|
||||
model?: SiteModel;
|
||||
modelName: string;
|
||||
modelRoute: string;
|
||||
action: string;
|
||||
method: number;
|
||||
}) {
|
||||
const {
|
||||
appEnv,
|
||||
userModelsProjectManager,
|
||||
userModelsClient,
|
||||
gitlabProjectModels,
|
||||
} = usePage().props as unknown as {
|
||||
appEnv: AppEnv;
|
||||
userModelsProjectManager: Array<UserModel>;
|
||||
userModelsClient: Array<UserModel>;
|
||||
gitlabProjectModels: Array<GitlabProjectModel>;
|
||||
};
|
||||
|
||||
/** Form section */
|
||||
const { post, setData, data } = useForm({
|
||||
name: model?.name ? model?.name[appEnv.locale] : undefined,
|
||||
url_re7: model?.url_re7 ?? undefined,
|
||||
url_prod: model?.url_prod ?? undefined,
|
||||
project_manager_id: model?.project_manager_id ?? undefined,
|
||||
gitlab_project_id: model?.gitlab_project_id ?? undefined,
|
||||
users:
|
||||
model && model.users
|
||||
? model?.users?.map((user) => String(user.id))
|
||||
: undefined,
|
||||
});
|
||||
|
||||
const submit = useCrudSubmit({
|
||||
method,
|
||||
modelRoute,
|
||||
modelName,
|
||||
action,
|
||||
model,
|
||||
post,
|
||||
data,
|
||||
modelNameTrans: useTrans(`models.classes.${modelName}`, {
|
||||
choice: 1,
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* Format collection before append to multi select input.
|
||||
* @param {Array<UserModel>} collection
|
||||
* @return {Array<MultiSelectOption>}
|
||||
*/
|
||||
function formatSelectOptions<T extends { id: number | string }>(
|
||||
collection: Array<T>,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
getLabel: (targetModel: T) => string,
|
||||
) {
|
||||
return collection.map((userModel: T) => {
|
||||
const updatedModel = {
|
||||
label: "",
|
||||
value: "",
|
||||
} as MultiSelectOption;
|
||||
updatedModel.label = getLabel(userModel);
|
||||
updatedModel.value = String(userModel.id);
|
||||
return updatedModel;
|
||||
});
|
||||
}
|
||||
|
||||
const selectOptionsProjectManager = formatSelectOptions(
|
||||
userModelsProjectManager,
|
||||
(userModel) => `${userModel.first_name} ${userModel.last_name}`,
|
||||
);
|
||||
const selectOptionsClient = formatSelectOptions(
|
||||
userModelsClient,
|
||||
(userModel) => `${userModel.first_name} ${userModel.last_name}`,
|
||||
);
|
||||
const selectOptionsProject = formatSelectOptions(
|
||||
gitlabProjectModels,
|
||||
(gitlabProjectModel) => gitlabProjectModel.name[appEnv.locale],
|
||||
);
|
||||
|
||||
return (
|
||||
<form id="config-form" onSubmit={submit}>
|
||||
<InputError all />
|
||||
<FieldsetWrapper
|
||||
title={useTrans("uwil-backend::back.form_title_global")}
|
||||
icon={<BookTextIcon className="mr-1" />}
|
||||
description={useTrans("uwil-backend::back.form_subtitle_global")}
|
||||
className="sm:grid-cols-2 gap-4"
|
||||
>
|
||||
<SimpleString
|
||||
name="name"
|
||||
label={str(
|
||||
useTrans("uwil-backend::back.form_associate_to", {
|
||||
params: {
|
||||
attribute: useTrans("validation.custom.name"),
|
||||
},
|
||||
choice: 1,
|
||||
}),
|
||||
)
|
||||
.ucFirst()
|
||||
.value()}
|
||||
placeholder="ex: John Doe"
|
||||
value={data.name}
|
||||
setData={setData}
|
||||
/>
|
||||
<MultiSelect
|
||||
name="project_manager_id"
|
||||
label={str(
|
||||
useTrans("uwil-backend::back.form_associate_to", {
|
||||
params: {
|
||||
attribute: useTrans("validation.custom.project_manager_id"),
|
||||
},
|
||||
choice: 1,
|
||||
}),
|
||||
)
|
||||
.ucFirst()
|
||||
.value()}
|
||||
placeholder={str(
|
||||
useTrans("back.select_element", {
|
||||
params: {
|
||||
element: useTrans("uwil-backend::models.classes.users", {
|
||||
choice: 1,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
)
|
||||
.ucFirst()
|
||||
.value()}
|
||||
options={selectOptionsProjectManager}
|
||||
multiple={false}
|
||||
defaultValue={
|
||||
data.project_manager_id && String(data.project_manager_id) !== ""
|
||||
? [String(data.project_manager_id)]
|
||||
: []
|
||||
}
|
||||
setData={setData}
|
||||
maxSelection={1}
|
||||
singleLine
|
||||
closeOnSelect
|
||||
closeButtonVisible
|
||||
/>
|
||||
<MultiSelect
|
||||
name="users"
|
||||
label={str(
|
||||
useTrans("uwil-backend::back.form_associate_to", {
|
||||
params: {
|
||||
attribute: useTrans("uwil-backend::models.classes.users", {
|
||||
choice: 2,
|
||||
}),
|
||||
},
|
||||
choice: 1,
|
||||
}),
|
||||
)
|
||||
.ucFirst()
|
||||
.value()}
|
||||
placeholder={str(
|
||||
useTrans("back.select_element", {
|
||||
params: {
|
||||
element: useTrans("uwil-backend::models.classes.users", {
|
||||
choice: 2,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
)
|
||||
.ucFirst()
|
||||
.value()}
|
||||
options={selectOptionsClient}
|
||||
defaultValue={data.users}
|
||||
setData={setData}
|
||||
closeButtonVisible
|
||||
/>
|
||||
<MultiSelect
|
||||
name="gitlab_project_id"
|
||||
label={str(
|
||||
useTrans("uwil-backend::back.form_associate_to", {
|
||||
params: {
|
||||
attribute: useTrans("models.classes.gitlab-projects", {
|
||||
choice: 1,
|
||||
}),
|
||||
},
|
||||
choice: 1,
|
||||
}),
|
||||
)
|
||||
.ucFirst()
|
||||
.value()}
|
||||
placeholder={str(
|
||||
useTrans("back.select_element", {
|
||||
params: {
|
||||
element: useTrans("models.classes.gitlab-projects", {
|
||||
choice: 1,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
)
|
||||
.ucFirst()
|
||||
.value()}
|
||||
options={selectOptionsProject}
|
||||
multiple={false}
|
||||
defaultValue={
|
||||
data.gitlab_project_id && String(data.gitlab_project_id) !== ""
|
||||
? [String(data.gitlab_project_id)]
|
||||
: []
|
||||
}
|
||||
setData={setData}
|
||||
maxSelection={1}
|
||||
singleLine
|
||||
closeOnSelect
|
||||
closeButtonVisible
|
||||
/>
|
||||
</FieldsetWrapper>
|
||||
<FieldsetWrapper
|
||||
title={useTrans("back.form_title_urls")}
|
||||
icon={<BookTextIcon className="mr-1" />}
|
||||
description={useTrans("back.form_subtitle_urls")}
|
||||
className="sm:grid-cols-2 gap-4"
|
||||
>
|
||||
<div className="flex justify-center items-end gap-1.5">
|
||||
<SimpleString
|
||||
name="url_re7"
|
||||
label={str(
|
||||
useTrans("uwil-backend::back.form_associate_to", {
|
||||
params: {
|
||||
attribute: useTrans("validation.custom.url_re7"),
|
||||
},
|
||||
choice: 2,
|
||||
}),
|
||||
)
|
||||
.ucFirst()
|
||||
.value()}
|
||||
type="url"
|
||||
placeholder="ex: https://example.com/"
|
||||
value={data.url_re7}
|
||||
setData={setData}
|
||||
required={false}
|
||||
/>
|
||||
{isAValidUrl(data.url_re7) && (
|
||||
<BtnOptionnalLinkWithTooltip
|
||||
content={<ExternalLinkIcon />}
|
||||
tooltipContent={GetTranslation("back.access_url")}
|
||||
link={data.url_re7}
|
||||
externalLink
|
||||
type="button"
|
||||
size="icon"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex justify-center items-end gap-1.5">
|
||||
<SimpleString
|
||||
name="url_prod"
|
||||
label={str(
|
||||
useTrans("uwil-backend::back.form_associate_to", {
|
||||
params: {
|
||||
attribute: useTrans("validation.custom.url_prod"),
|
||||
},
|
||||
choice: 12,
|
||||
}),
|
||||
)
|
||||
.ucFirst()
|
||||
.value()}
|
||||
type="url"
|
||||
placeholder="ex: https://example.com/"
|
||||
value={data.url_prod}
|
||||
setData={setData}
|
||||
required={false}
|
||||
/>
|
||||
{isAValidUrl(data.url_prod) && (
|
||||
<BtnOptionnalLinkWithTooltip
|
||||
content={<ExternalLinkIcon />}
|
||||
tooltipContent={GetTranslation("back.access_url")}
|
||||
link={data.url_prod}
|
||||
externalLink
|
||||
type="button"
|
||||
size="icon"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</FieldsetWrapper>
|
||||
<div className="text-center">
|
||||
<SubmitButton
|
||||
modelTrans={useTrans(`models.classes.${modelName}`, {
|
||||
choice: 1,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
316
resources/ts/back/Pages/Sites/Index.tsx
Normal file
316
resources/ts/back/Pages/Sites/Index.tsx
Normal file
|
|
@ -0,0 +1,316 @@
|
|||
// * Packages
|
||||
import { route } from "ziggy-js";
|
||||
import { usePage } from "@inertiajs/react";
|
||||
import { Row } from "@tanstack/react-table";
|
||||
import {
|
||||
ArchiveIcon,
|
||||
ArrowLeftIcon,
|
||||
PlusIcon,
|
||||
TriangleAlertIcon,
|
||||
UserIcon,
|
||||
} from "lucide-react";
|
||||
// * Components shadcn
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
// * Other
|
||||
import AuthenticatedLayout from "@/back/Layouts/AuthenticatedLayout";
|
||||
import DataTable from "@/back/Components/Table/DataTable";
|
||||
import DataTableSearch from "@/back/Components/Table/DataTableSearch";
|
||||
import DataTableFilterButton from "@/back/Components/Table/DataTableFilterButton";
|
||||
import SoftdeleteWarning from "@/back/Components/SoftdeleteWarning";
|
||||
import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip";
|
||||
import { GetFormatedDate, GetTranslation } from "@/lib/utils";
|
||||
import { Views } from "@/back/lib/actions";
|
||||
import { AuthProp, Resource } from "@/back/types/global";
|
||||
import str from "@/hooks/use-string";
|
||||
import useTrans from "@/hooks/use-translations";
|
||||
import { SiteModel } from "@/back/types/project";
|
||||
import RenderTranslationCell from "@/back/Components/Table/RenderTranslationCell";
|
||||
|
||||
const getColumns = (viewAnyUsers: boolean) => {
|
||||
return [
|
||||
{
|
||||
accessorKey: "name",
|
||||
header: ({ column }: { column: { id: string } }) => (
|
||||
<DataTableFilterButton
|
||||
label={str(GetTranslation(`validation.custom.${column.id}`))
|
||||
.ucFirst()
|
||||
.value()}
|
||||
field={column.id}
|
||||
/>
|
||||
),
|
||||
cell: ({ row }: { row: Row<SiteModel> }) => (
|
||||
<RenderTranslationCell value={row.getValue("name")} />
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "url_re7",
|
||||
header: ({ column }: { column: { id: string } }) => (
|
||||
<p className="text-center">
|
||||
{str(GetTranslation(`validation.custom.${column.id}`))
|
||||
.ucFirst()
|
||||
.value()}
|
||||
</p>
|
||||
),
|
||||
cell: ({ row }: { row: Row<SiteModel> }) =>
|
||||
row.original.url_re7 ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<a
|
||||
href={row.original.url_re7}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-primary underline underline-offset-4 hover:opacity-80 transition-opacity rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
||||
>
|
||||
{row.original.url_re7}
|
||||
</a>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{GetTranslation("back.access_url")}</TooltipContent>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<p className="text-muted-foreground italic m-0">
|
||||
{GetTranslation("uwil-backend::back.table_not_known")}
|
||||
</p>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "url_prod",
|
||||
header: ({ column }: { column: { id: string } }) => (
|
||||
<p className="text-center">
|
||||
{str(GetTranslation(`validation.custom.${column.id}`))
|
||||
.ucFirst()
|
||||
.value()}
|
||||
</p>
|
||||
),
|
||||
cell: ({ row }: { row: Row<SiteModel> }) =>
|
||||
row.original.url_prod ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<a
|
||||
href={row.original.url_prod}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-primary underline underline-offset-4 hover:opacity-80 transition-opacity rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
||||
>
|
||||
{row.original.url_prod}
|
||||
</a>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{GetTranslation("back.access_url")}</TooltipContent>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<p className="text-muted-foreground italic m-0">
|
||||
{GetTranslation("uwil-backend::back.table_not_known")}
|
||||
</p>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "project_manager_id",
|
||||
header: ({ column }: { column: { id: string } }) => (
|
||||
<DataTableFilterButton
|
||||
label={str(GetTranslation(`validation.custom.${column.id}`))
|
||||
.ucFirst()
|
||||
.value()}
|
||||
field={column.id}
|
||||
/>
|
||||
),
|
||||
cell: ({ row }: { row: Row<SiteModel> }) => {
|
||||
if (row.original.project_manager) {
|
||||
const userModel = row.original.project_manager;
|
||||
const content = (
|
||||
<span>
|
||||
{userModel.first_name} {userModel.last_name}
|
||||
</span>
|
||||
);
|
||||
let value = (
|
||||
<div className="flex flex-row justify-center items-center gap-1.5">
|
||||
<Badge variant="outline">{content}</Badge>
|
||||
{userModel.deleted_at !== null && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<TriangleAlertIcon size={15} className="text-destructive" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{GetTranslation("back.model_archived", {
|
||||
params: {
|
||||
model: str(
|
||||
GetTranslation("uwil-backend::models.classes.users", {
|
||||
choice: 1,
|
||||
}),
|
||||
)
|
||||
.ucFirst()
|
||||
.value(),
|
||||
},
|
||||
choice: 1,
|
||||
})}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
if (viewAnyUsers && userModel.deleted_at === null) {
|
||||
value = (
|
||||
<BtnOptionnalLinkWithTooltip
|
||||
link={route("back.users.show", userModel)}
|
||||
content={
|
||||
<>
|
||||
<UserIcon />
|
||||
{content}
|
||||
</>
|
||||
}
|
||||
tooltipContent={
|
||||
<p>
|
||||
{GetTranslation("uwil-backend::back.access_model", {
|
||||
params: {
|
||||
model: GetTranslation(
|
||||
"uwil-backend::models.classes.users",
|
||||
{
|
||||
choice: 1,
|
||||
},
|
||||
),
|
||||
},
|
||||
})}
|
||||
</p>
|
||||
}
|
||||
type="button"
|
||||
size="default"
|
||||
variant="secondary"
|
||||
/>
|
||||
);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
return (
|
||||
<p className="text-muted-foreground italic m-0">
|
||||
{GetTranslation("uwil-backend::back.table_not_known")}
|
||||
</p>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "updated_at",
|
||||
header: ({ column }: { column: { id: string } }) => (
|
||||
<DataTableFilterButton
|
||||
label={str(
|
||||
GetTranslation(`uwil-backend::validation.attributes.${column.id}`),
|
||||
)
|
||||
.ucFirst()
|
||||
.value()}
|
||||
field={column.id}
|
||||
/>
|
||||
),
|
||||
cell: ({ row }: { row: Row<SiteModel> }) => (
|
||||
<Badge variant="secondary">
|
||||
{GetFormatedDate(row.getValue("updated_at"))}
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export default function Index({
|
||||
models,
|
||||
searchFields,
|
||||
modelName,
|
||||
}: {
|
||||
models: Resource;
|
||||
searchFields: string;
|
||||
modelName: string;
|
||||
}) {
|
||||
const { auth } = usePage().props as unknown as {
|
||||
auth: AuthProp;
|
||||
};
|
||||
const { viewAny, viewTrash, create } = auth.policies.sites;
|
||||
const { viewAny: viewAnyUsers } = auth.policies.users;
|
||||
const isArchiveRoute = route().current(`back.${modelName}.trash`);
|
||||
const actionModels = isArchiveRoute
|
||||
? [Views.RESTORATION, Views.FORCEDELETION]
|
||||
: [
|
||||
Views.VISUALIZATION,
|
||||
Views.EDITION,
|
||||
Views.DUPLICATION,
|
||||
Views.ARCHIVATION,
|
||||
];
|
||||
const columns = getColumns(viewAnyUsers);
|
||||
|
||||
// Translations.
|
||||
const transModel = useTrans(`models.classes.${modelName}`, { choice: 2 });
|
||||
const transArchiveTitle = useTrans(
|
||||
"uwil-backend::crud.helpers.archives_model",
|
||||
{
|
||||
params: { model: transModel },
|
||||
},
|
||||
);
|
||||
const transTitle = str(isArchiveRoute ? transArchiveTitle : transModel)
|
||||
.ucFirst()
|
||||
.value();
|
||||
const transCrudBackList = useTrans("uwil-backend::back.crud_back_to_list");
|
||||
|
||||
const beforeNode = () => {
|
||||
return (
|
||||
isArchiveRoute &&
|
||||
viewAny && (
|
||||
<div className="flex justify-end items-center gap-x-1.5">
|
||||
<BtnOptionnalLinkWithTooltip
|
||||
link={route(`back.${modelName}.index`)}
|
||||
content={<ArrowLeftIcon />}
|
||||
tooltipContent={<p>{transCrudBackList}</p>}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
};
|
||||
const transCrudAddEntry = useTrans("uwil-backend::back.crud_add_entry");
|
||||
const transAccessTo = useTrans("uwil-backend::back.crud_access_to", {
|
||||
params: {
|
||||
target: useTrans("uwil-backend::back.archived_entries"),
|
||||
},
|
||||
});
|
||||
const afterNode = () => {
|
||||
return (
|
||||
!isArchiveRoute && (
|
||||
<div className="flex justify-end items-center gap-x-1.5">
|
||||
{viewTrash && (
|
||||
<BtnOptionnalLinkWithTooltip
|
||||
link={route(`back.${modelName}.trash`)}
|
||||
content={<ArchiveIcon />}
|
||||
tooltipContent={<p>{transAccessTo}</p>}
|
||||
variant="outline"
|
||||
/>
|
||||
)}
|
||||
{create && (
|
||||
<BtnOptionnalLinkWithTooltip
|
||||
link={route(`back.${modelName}.create`)}
|
||||
content={<PlusIcon />}
|
||||
tooltipContent={<p>{transCrudAddEntry}</p>}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthenticatedLayout
|
||||
title={transTitle}
|
||||
beforeNode={beforeNode()}
|
||||
afterNode={afterNode()}
|
||||
>
|
||||
{isArchiveRoute && <SoftdeleteWarning />}
|
||||
<DataTableSearch
|
||||
modelRoute={modelName}
|
||||
searchFields={searchFields}
|
||||
isArchiveRoute={isArchiveRoute}
|
||||
/>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
modelRoute={modelName}
|
||||
modelsPaginate={models}
|
||||
actions={actionModels}
|
||||
/>
|
||||
</AuthenticatedLayout>
|
||||
);
|
||||
}
|
||||
341
resources/ts/back/Pages/Sites/Show.tsx
Normal file
341
resources/ts/back/Pages/Sites/Show.tsx
Normal file
|
|
@ -0,0 +1,341 @@
|
|||
// * Packages
|
||||
import { usePage } from "@inertiajs/react";
|
||||
import { route } from "ziggy-js";
|
||||
import {
|
||||
ArrowLeftIcon,
|
||||
FolderGitIcon,
|
||||
TriangleAlertIcon,
|
||||
UserIcon,
|
||||
} from "lucide-react";
|
||||
// * Components shadcn
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
// * Other
|
||||
import AuthenticatedLayout from "@/back/Layouts/AuthenticatedLayout";
|
||||
import DetailsTable from "@/back/Components/Table/DetailsTable";
|
||||
import ModelActions from "@/back/Components/ModelActions";
|
||||
import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip";
|
||||
import RenderTranslationCell from "@/back/Components/Table/RenderTranslationCell";
|
||||
import { GetFormatedDate, GetTranslation } from "@/lib/utils";
|
||||
import { Views } from "@/back/lib/actions";
|
||||
import { AuthProp, SingleResource, UserModel } from "@/back/types/global";
|
||||
import str from "@/hooks/use-string";
|
||||
import useTrans from "@/hooks/use-translations";
|
||||
import { AppEnv } from "@/types/global";
|
||||
import { TranslationField } from "@/back/types/i18n";
|
||||
import { GitlabProjectModel, SiteModel } from "@/back/types/project";
|
||||
|
||||
const getColumns = (viewAnyUsers: boolean, appEnv: AppEnv) => {
|
||||
return [
|
||||
{
|
||||
accessorKey: "name",
|
||||
header: str(GetTranslation("validation.custom.name")).ucFirst().value(),
|
||||
cell: ({ value }: { value: TranslationField }) => (
|
||||
<RenderTranslationCell value={value} />
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "url_re7",
|
||||
header: str(GetTranslation("validation.custom.url_re7"))
|
||||
.ucFirst()
|
||||
.value(),
|
||||
cell: ({ value }: { value: string | null }) =>
|
||||
value ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<a
|
||||
href={value}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-primary underline underline-offset-4 hover:opacity-80 transition-opacity rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
||||
>
|
||||
{value}
|
||||
</a>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{GetTranslation("back.access_url")}</TooltipContent>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<p>{GetTranslation("uwil-backend::trans.table_not_known")}</p>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "url_prod",
|
||||
header: str(GetTranslation("validation.custom.url_prod"))
|
||||
.ucFirst()
|
||||
.value(),
|
||||
cell: ({ value }: { value: string | null }) =>
|
||||
value ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<a
|
||||
href={value}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="text-primary underline underline-offset-4 hover:opacity-80 transition-opacity rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
|
||||
>
|
||||
{value}
|
||||
</a>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>{GetTranslation("back.access_url")}</TooltipContent>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<p>{GetTranslation("uwil-backend::trans.table_not_known")}</p>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "project_manager",
|
||||
header: str(GetTranslation("validation.custom.project_manager_id"))
|
||||
.ucFirst()
|
||||
.value(),
|
||||
cell: ({ value }: { value: UserModel | null }) => {
|
||||
if (value) {
|
||||
const userData = (
|
||||
<span>
|
||||
{value.first_name} {value.last_name}
|
||||
</span>
|
||||
);
|
||||
let content = (
|
||||
<div className="flex flex-row justify-start items-center">
|
||||
<Badge variant="outline">{userData}</Badge>
|
||||
{value.deleted_at !== null && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<TriangleAlertIcon
|
||||
size={15}
|
||||
className="text-destructive ms-1"
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{GetTranslation("back.model_archived", {
|
||||
params: {
|
||||
model: str(
|
||||
GetTranslation("uwil-backend::models.classes.users", {
|
||||
choice: 1,
|
||||
}),
|
||||
)
|
||||
.ucFirst()
|
||||
.value(),
|
||||
},
|
||||
choice: 1,
|
||||
})}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
if (viewAnyUsers && value.deleted_at === null) {
|
||||
content = (
|
||||
<BtnOptionnalLinkWithTooltip
|
||||
link={route("back.users.show", value)}
|
||||
content={
|
||||
<>
|
||||
<UserIcon />
|
||||
{userData}
|
||||
</>
|
||||
}
|
||||
tooltipContent={
|
||||
<p>
|
||||
{GetTranslation("uwil-backend::back.access_model", {
|
||||
params: {
|
||||
model: GetTranslation(
|
||||
"uwil-backend::models.classes.users",
|
||||
{
|
||||
choice: 1,
|
||||
},
|
||||
),
|
||||
},
|
||||
})}
|
||||
</p>
|
||||
}
|
||||
type="button"
|
||||
size="default"
|
||||
variant="secondary"
|
||||
/>
|
||||
);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
return (
|
||||
<p className="text-muted-foreground italic m-0">
|
||||
{GetTranslation("uwil-backend::back.table_not_known")}
|
||||
</p>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "gitlab_project",
|
||||
header: str(
|
||||
GetTranslation("models.classes.gitlab-projects", {
|
||||
choice: 1,
|
||||
}),
|
||||
)
|
||||
.ucFirst()
|
||||
.value(),
|
||||
cell: ({ value }: { value: GitlabProjectModel | null }) => {
|
||||
if (value) {
|
||||
const projectData = <span>{value.name[appEnv.locale]}</span>;
|
||||
let content = (
|
||||
<div className="flex flex-row justify-start items-center">
|
||||
<Badge variant="outline">{projectData}</Badge>
|
||||
{value.deleted_at !== null && (
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<TriangleAlertIcon
|
||||
size={15}
|
||||
className="text-destructive ms-1"
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{GetTranslation("back.model_archived", {
|
||||
params: {
|
||||
model: str(
|
||||
GetTranslation("models.classes.gitlab-projects", {
|
||||
choice: 1,
|
||||
}),
|
||||
)
|
||||
.ucFirst()
|
||||
.value(),
|
||||
},
|
||||
choice: 1,
|
||||
})}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
if (viewAnyUsers && value.deleted_at === null) {
|
||||
content = (
|
||||
<BtnOptionnalLinkWithTooltip
|
||||
link={route("back.gitlab-projects.show", value)}
|
||||
content={
|
||||
<>
|
||||
<FolderGitIcon />
|
||||
{projectData}
|
||||
</>
|
||||
}
|
||||
tooltipContent={
|
||||
<p>
|
||||
{GetTranslation("uwil-backend::back.access_model", {
|
||||
params: {
|
||||
model: GetTranslation(
|
||||
"models.classes.gitlab-projects",
|
||||
{
|
||||
choice: 1,
|
||||
},
|
||||
),
|
||||
},
|
||||
})}
|
||||
</p>
|
||||
}
|
||||
type="button"
|
||||
size="default"
|
||||
variant="secondary"
|
||||
/>
|
||||
);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
return (
|
||||
<p className="text-muted-foreground italic m-0">
|
||||
{GetTranslation("uwil-backend::back.table_not_known")}
|
||||
</p>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "created_at",
|
||||
header: str(
|
||||
GetTranslation("uwil-backend::validation.attributes.created_at"),
|
||||
)
|
||||
.ucFirst()
|
||||
.value(),
|
||||
cell: ({ value }: { value: string }) => (
|
||||
<Badge variant="secondary">{GetFormatedDate(value)}</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "updated_at",
|
||||
header: str(
|
||||
GetTranslation("uwil-backend::validation.attributes.updated_at"),
|
||||
)
|
||||
.ucFirst()
|
||||
.value(),
|
||||
cell: ({ value }: { value: string }) => (
|
||||
<Badge variant="secondary">{GetFormatedDate(value)}</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "deleted_at",
|
||||
header: str(
|
||||
GetTranslation("uwil-backend::validation.attributes.deleted_at"),
|
||||
)
|
||||
.ucFirst()
|
||||
.value(),
|
||||
cell: ({ value }: { value: string }) => (
|
||||
<Badge variant="secondary">{GetFormatedDate(value)}</Badge>
|
||||
),
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export default function Show({
|
||||
data,
|
||||
modelName,
|
||||
}: {
|
||||
data: SingleResource;
|
||||
modelName: string;
|
||||
}) {
|
||||
const { auth, appEnv } = usePage().props as unknown as {
|
||||
auth: AuthProp;
|
||||
appEnv: AppEnv;
|
||||
};
|
||||
const { viewAny } = auth.policies.sites;
|
||||
const { viewAny: viewAnyUsers } = auth.policies.users;
|
||||
const model = data.data as SiteModel;
|
||||
const actionModels = [Views.EDITION, Views.DUPLICATION, Views.ARCHIVATION];
|
||||
const transTitle = useTrans(
|
||||
"uwil-backend::crud.helpers.visualization_model",
|
||||
{
|
||||
params: {
|
||||
model: useTrans(`models.classes.${modelName}`, {
|
||||
choice: 1,
|
||||
}),
|
||||
},
|
||||
},
|
||||
);
|
||||
const columns = getColumns(viewAnyUsers, appEnv);
|
||||
|
||||
// Translations.
|
||||
const transCrudBackToList = useTrans("uwil-backend::back.crud_back_to_list");
|
||||
|
||||
const beforeNode = () => {
|
||||
return (
|
||||
viewAny && (
|
||||
<div className="flex justify-end items-center gap-x-1.5">
|
||||
<BtnOptionnalLinkWithTooltip
|
||||
link={route(`back.${modelName}.index`)}
|
||||
content={<ArrowLeftIcon />}
|
||||
tooltipContent={<p>{transCrudBackToList}</p>}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthenticatedLayout title={transTitle} beforeNode={beforeNode()}>
|
||||
<ModelActions
|
||||
actions={actionModels}
|
||||
modelRoute={modelName}
|
||||
model={model}
|
||||
modelUpdatedAt={model.updated_at.toString()}
|
||||
/>
|
||||
{/* @ts-expect-error Weird error type on cell. */}
|
||||
<DetailsTable columns={columns} model={model} />
|
||||
</AuthenticatedLayout>
|
||||
);
|
||||
}
|
||||
14
routes/back/sites.php
Normal file
14
routes/back/sites.php
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
use App\Http\Controllers\Back\SiteController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::get('/sites/trash', [SiteController::class, 'index'])
|
||||
->name('sites.trash');
|
||||
Route::patch('/sites/{site}/restore', [SiteController::class, 'restore'])
|
||||
->name('sites.restore');
|
||||
Route::delete('/sites/{site}/force', [SiteController::class, 'forceDestroy'])
|
||||
->name('sites.forceDestroy');
|
||||
Route::resource('sites', SiteController::class);
|
||||
Route::get('/sites/{site}/duplicate', [SiteController::class, 'duplicate'])
|
||||
->name('sites.duplicate');
|
||||
45
routes/breadcrumbs/sites.php
Normal file
45
routes/breadcrumbs/sites.php
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
use Diglactic\Breadcrumbs\Breadcrumbs;
|
||||
use Diglactic\Breadcrumbs\Generator;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
Breadcrumbs::for('back.sites.index', function (Generator $trail) {
|
||||
$trail->push(
|
||||
Str::ucfirst(trans_choice('models.classes.sites', \INF)),
|
||||
route('back.sites.index')
|
||||
);
|
||||
});
|
||||
Breadcrumbs::for('back.sites.create', function (Generator $trail) {
|
||||
$trail->parent('back.sites.index');
|
||||
$trail->push(
|
||||
Str::ucfirst(trans('uwil-backend::crud.actions.create')),
|
||||
route('back.sites.create'),
|
||||
);
|
||||
});
|
||||
Breadcrumbs::for('back.sites.edit', function (Generator $trail) {
|
||||
$trail->parent('back.sites.index');
|
||||
$trail->push(
|
||||
Str::ucfirst(trans('uwil-backend::crud.actions.edit')),
|
||||
route('back.sites.edit', [
|
||||
'site' => request()->site,
|
||||
]),
|
||||
);
|
||||
});
|
||||
Breadcrumbs::for('back.sites.duplicate', function (Generator $trail) {
|
||||
$trail->parent('back.sites.index');
|
||||
$trail->push(Str::ucfirst(trans('uwil-backend::crud.actions.duplicate')));
|
||||
});
|
||||
Breadcrumbs::for('back.sites.show', function (Generator $trail) {
|
||||
$trail->parent('back.sites.index');
|
||||
$trail->push(
|
||||
Str::ucfirst(trans('uwil-backend::crud.actions.visualize')),
|
||||
route('back.sites.show', [
|
||||
'site' => request()->site,
|
||||
]),
|
||||
);
|
||||
});
|
||||
Breadcrumbs::for('back.sites.trash', function (Generator $trail) {
|
||||
$trail->parent('back.sites.index');
|
||||
$trail->push(Str::ucfirst(trans('uwil-backend::back.archives')));
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue