feat: ✨ clean project (policies, translations, breadcrumbs, dead code etc...)

This commit is contained in:
Alexis Gousseau 2025-05-26 11:33:47 +02:00
parent b5d46f54ee
commit b13a4ced30
143 changed files with 3774 additions and 2373 deletions

View file

@ -8,3 +8,6 @@
 
- continue migrations :
`php artisan migrate`
TODO:
- sur les méthodes GET le token est visible dans les logs server..

View file

@ -28,18 +28,18 @@ class CreateAdmin extends Command
protected $description = 'Create a backend admin user';
/**
* The firstname of the new user.
* The first name of the new user.
*
* @var string|null
*/
protected $firstname;
protected $first_name;
/**
* The lastname of the new user.
* The last name of the new user.
*
* @var string|null
*/
protected $lastname;
protected $last_name;
/**
* The email of the new user.
@ -48,13 +48,6 @@ class CreateAdmin extends Command
*/
protected $email;
/**
* The password of the new user.
*
* @var string|null
*/
protected $password;
/**
* The role of the new user.
*
@ -69,27 +62,23 @@ class CreateAdmin extends Command
*/
public function handle(): int
{
$this->firstname = null;
$this->first_name = null;
$this->addFirstname();
$this->lastname = null;
$this->last_name = null;
$this->addLastname();
$this->email = null;
$this->addEmail();
$this->password = null;
$this->addPassword();
$this->role = null;
$this->addRole();
$user = new User([
'firstname' => $this->firstname,
'lastname' => $this->lastname,
'first_name' => $this->first_name,
'last_name' => $this->last_name,
'email' => $this->email,
'email_verified_at' => now(),
'password' => $this->password,
'role' => $this->role,
]);
$user->saveOrFail();
@ -106,14 +95,14 @@ class CreateAdmin extends Command
*/
private function addFirstname(): void
{
while (is_null($this->firstname)) {
while (is_null($this->first_name)) {
try {
$tmp = $this->ask('Type the wanted user firstname', 'Admin');
$validator = Validator::make(['firstname' => $tmp], [
'firstname' => 'required|min:3|max:255'
]);
$validator->validate();
$this->firstname = $tmp;
$this->first_name = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please provide a valid firstname %s',
@ -131,14 +120,14 @@ class CreateAdmin extends Command
*/
private function addLastname(): void
{
while (is_null($this->lastname)) {
while (is_null($this->last_name)) {
try {
$tmp = $this->ask('Type the wanted user lastname', 'Saïtama');
$validator = Validator::make(['lastname' => $tmp], [
'lastname' => 'required|min:3|max:255'
]);
$validator->validate();
$this->lastname = $tmp;
$this->last_name = $tmp;
} catch (ValidationException $e) {
$this->error(sprintf(
'Please provide a valid lastname %s',
@ -174,22 +163,6 @@ class CreateAdmin extends Command
}; //end while
}
/**
* Add a password to the user.
*
* @return void
*/
private function addPassword(): void
{
while (
($this->password = $this->secret(
(!\is_null($this->password) ? 'Failed to confirm, ' : '') . 'Type the wanted user password'
)) !== $this->secret('Confirm the password')
) {
continue;
}; //end while
}
/**
* Add a role to the user.
*

View file

@ -6,19 +6,26 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\AppUser\StoreAppUserRequest;
use App\Http\Requests\Bo\AppUser\UpdateAppUserRequest;
use App\Http\Requests\Bo\ImportRequest;
use App\Http\Requests\Bo\UpdateChangeOrderRequest;
use App\Imports\AppUserImport;
use App\Models\AppUser;
use App\Models\Directory;
use App\Traits\Controllers\ChangesModelOrder;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Session;
use Illuminate\Support\Str;
use Maatwebsite\Excel\Facades\Excel;
class AppUserController extends Controller
{
use ChangesModelOrder;
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(AppUser::class);
}
/**
* Display a listing of the resource.
*
@ -49,23 +56,27 @@ class AppUserController extends Controller
/** Sort columns with a query */
$this->sortQuery($query);
$app_users = $this->paginate($query);
$appUserModels = $this->paginate($query);
return view('bo.pages.app-users.index', compact('app_users', 'searchFields'));
return view('bo.pages.app_users.index', compact('appUserModels', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\AppUser $app_user
* @return \Illuminate\View\View
*/
public function create(): \Illuminate\View\View
public function create(AppUser $app_user): \Illuminate\View\View
{
$directories = Directory::renderDirectories(
Directory::query()->where('needAuthorization', 1)->orderBy('name', 'ASC')->get()
);
return view('bo.pages.app-users.create', compact('directories'));
return view('bo.pages.app_users.create', [
'appUserModel' => $app_user,
'directories' => $directories
]);
}
/**
@ -83,10 +94,10 @@ class AppUserController extends Controller
if ($app_user->saveOrFail()) {
$app_user->directories()->sync(collect($request->directories)->pluck('id'));
return redirect()
->route('bo.app-users.edit', ['tenant' => \tenant(), 'app_user' => $app_user])
->route('bo.app_users.edit', ['tenant' => \tenant(), 'app_user' => $app_user])
->with('success', trans(
':model a été créé',
['model' => Str::singular(trans('models.classes.app_users'))]
['model' => trans('models.classes.app_user')]
));
}
@ -94,7 +105,7 @@ class AppUserController extends Controller
->back()
->with('error', trans(
':model n\'a pas été créé',
['model' => Str::singular(trans('models.classes.app_users'))]
['model' => trans('models.classes.app_user')]
));
});
}
@ -113,9 +124,9 @@ class AppUserController extends Controller
// @phpstan-ignore assign.propertyReadOnly
$app_user->directories = Directory::renderDirectories($app_user->directories);
return view('bo.pages.app-users.edit', [
'app_userModel' => $app_user,
'directories' => $directories
return view('bo.pages.app_users.edit', [
'appUserModel' => $app_user,
'directories' => $directories
]);
}
@ -138,17 +149,17 @@ class AppUserController extends Controller
if ($app_user->saveOrFail()) {
return redirect()
->route('bo.app-users.edit', ['tenant' => \tenant(), 'app_user' => $app_user])
->route('bo.app_users.edit', ['tenant' => \tenant(), 'app_user' => $app_user])
->with('success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.app_users'))]
['model' => trans('models.classes.app_user')]
));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été mis à jour',
['model' => Str::singular(trans('models.classes.app_users'))]
['model' => trans('models.classes.app_user')]
));
});
}
@ -163,10 +174,10 @@ class AppUserController extends Controller
{
if ($app_user->deleteOrFail()) {
return redirect()
->route('bo.app-users.index', ['tenant' => \tenant()])
->route('bo.app_users.index', ['tenant' => \tenant()])
->with('warning', trans(
':model a bien été supprimé',
['model' => Str::singular(trans('models.classes.app_users'))]
['model' => trans('models.classes.app_user')]
));
}
@ -174,10 +185,21 @@ class AppUserController extends Controller
->back()
->with('error', trans(
':model n\'a pas été supprimé',
['model' => Str::singular(trans('models.classes.app_users'))]
['model' => trans('models.classes.app_user')]
));
}
/**
* Duplicate the specified resource.
*
* @param \App\Models\AppUser $app_user
* @return \Illuminate\Contracts\View\View
*/
public function duplicate(AppUser $app_user): \Illuminate\Contracts\View\View
{
return $this->create($app_user->replicate());
}
/**
* Mass import by CSV.
* matricule;lastname;firstname;email
@ -190,68 +212,7 @@ class AppUserController extends Controller
{
Excel::import(new AppUserImport(), $request->import, null, \Maatwebsite\Excel\Excel::CSV);
return redirect()->route('bo.app-users.index', ['tenant' => \tenant()])->with('success', 'Import terminé.');
}
/**
* Change order.
*
* @param \App\Http\Requests\Bo\UpdateChangeOrderRequest $request
* @param mixed ...$params
* @return \Illuminate\Http\RedirectResponse
* @throws \RuntimeException Si le nom du parametre de la route ne correspond pas au params utilisé.
*/
public function changeOrder(UpdateChangeOrderRequest $request, mixed ...$params): \Illuminate\Http\RedirectResponse
{
$paramNames = collect(request()->route()->parameterNames);
$className = Str::of($paramNames->get($paramNames->count() - 2))->lower()->studly();
$className = "\\App\\Models\\AppUser";
$modelId = Arr::get($params, $paramNames->count() - 2);
$model = AppUser::find($modelId);
if (!class_exists($className)) {
throw new \RuntimeException("The {$className} class doesn't exist.");
}
if (!\is_object($model)) {
throw new \RuntimeException("The second last param isn't an object.");
}
if (get_class($model) !== (string) Str::of($className)->replaceStart('\\', '')) {
throw new \RuntimeException("The {$className} doesn't match second last binded param.");
}
/** @var \Illuminate\Database\Eloquent\Model|null */
$model = $className::query()
->where('tenant_id', \tenant())
->where($model->getRouteKeyName(), $model->getRouteKey())
->first();
if (!$model) {
return back()->with('error', trans('crud.messages.order_not_changed'));
}
return DB::transaction(function () use ($request, $className, $model) {
/** @var \Illuminate\Database\Eloquent\Model|null */
$tmp = $className::query()
->where('tenant_id', \tenant())
->where('order', $request->direction ? '<' : '>', $model->getOriginal('order'))
->orderBy('order', $request->direction ? 'DESC' : 'ASC')->first();
if (!$tmp) {
return back()->with('error', trans('crud.messages.order_not_changed'));
}
$newOrder = $tmp->getOriginal('order');
// @phpstan-ignore property.notFound
$tmp->order = $model->getOriginal('order');
$tmp->saveQuietly();
// @phpstan-ignore property.notFound
$model->order = $newOrder;
$model->saveQuietly();
Session::flash('success', trans('crud.messages.order_changed'));
return back();
});
return redirect()->route('bo.app_users.index', ['tenant' => \tenant()])
->with('success', 'Import terminé.');
}
}

View file

@ -11,6 +11,14 @@ use Illuminate\Database\Eloquent\Builder;
class ContactController extends Controller
{
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(Contact::class);
}
/**
* Display a listing of the resource.
*
@ -49,11 +57,11 @@ class ContactController extends Controller
});
}
$contactModelList = $this->paginate($query);
$contactModels = $this->paginate($query);
$appUsers = AppUser::query()->select(['id', 'lastname', 'firstname'])->orderBy('lastname')->get();
return view('bo.pages.contacts.index', compact('contactModelList', 'searchFields', 'appUsers'));
return view('bo.pages.contacts.index', compact('contactModels', 'searchFields', 'appUsers'));
}
/**

View file

@ -9,10 +9,17 @@ use App\Models\Directory;
use App\Models\Setting;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class DirectoriesController extends Controller
{
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(Directory::class);
}
/**
* Show the form for creating a new resource.
*
@ -20,8 +27,8 @@ class DirectoriesController extends Controller
*/
public function index(): \Illuminate\Contracts\View\View
{
$directories = $this->getDirectoriesForComponent();
return \view('bo.pages.directories.index', \compact('directories'));
$directoryModels = $this->getDirectoriesForComponent();
return \view('bo.pages.directories.index', \compact('directoryModels'));
}
/**
@ -40,14 +47,14 @@ class DirectoriesController extends Controller
->route('bo.directories.index', ['tenant' => \tenant()])
->with('success', trans(
':model a été créé',
['model' => Str::singular(trans('models.classes.directories'))]
['model' => trans('models.classes.directory')]
));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été créé',
['model' => Str::singular(trans('models.classes.directories'))]
['model' => trans('models.classes.directory')]
));
});
}
@ -60,7 +67,6 @@ class DirectoriesController extends Controller
*/
public function edit(Directory $directory): \Illuminate\View\View
{
$this->authorize('edit', $directory);
return view('bo.pages.directories.edit', ['directoryModel' => $directory]);
}
@ -73,7 +79,6 @@ class DirectoriesController extends Controller
*/
public function update(UpdateDirectoryRequest $request, Directory $directory): \Illuminate\Http\RedirectResponse
{
$this->authorize('edit', $directory);
return DB::transaction(function () use ($request, $directory) {
$directory->fill($request->validated());
@ -82,14 +87,14 @@ class DirectoriesController extends Controller
->route('bo.directories.edit', ['tenant' => \tenant(), 'directory' => $directory])
->with('success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.directories'))]
['model' => trans('models.classes.directory')]
));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été mis à jour',
['model' => Str::singular(trans('models.classes.directories'))]
['model' => trans('models.classes.directory')]
));
});
}
@ -102,7 +107,6 @@ class DirectoriesController extends Controller
*/
public function destroy(Directory $directory): \Illuminate\Database\Eloquent\Collection
{
$this->authorize('delete', $directory);
return DB::transaction(function () use ($directory) {
/** @var \App\Models\Directory $directoryChild */
foreach ($directory->children as $directoryChild) {
@ -126,7 +130,7 @@ class DirectoriesController extends Controller
':model :name n\'a pas été supprimé,
un bouton est encore associé à ce dernier dans les paramètres :',
[
'model' => Str::singular(trans('models.classes.directories')),
'model' => trans('models.classes.directory'),
'name' => $directory->name['fr']
]
)

View file

@ -25,6 +25,14 @@ class DocumentController extends Controller
{
use ChangesModelOrder;
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(Document::class);
}
/**
* Display a listing of the resource.
*
@ -57,28 +65,34 @@ class DocumentController extends Controller
/** Sort columns with a query */
$this->sortQuery($query);
$documents = $this->paginate($query);
$documentModels = $this->paginate($query);
return view('bo.pages.documents.index', [
'documents' => $documents,
'searchFields' => $searchFields,
'searchTag' => $request->tag,
'tags' => $tags
'documentModels' => $documentModels,
'searchFields' => $searchFields,
'searchTag' => $request->tag,
'tags' => $tags
]);
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\Document $document
* @return \Illuminate\View\View
*/
public function create(): \Illuminate\View\View
public function create(Document $document): \Illuminate\View\View
{
$documents = Document::where('lang', LangEnum::fr)->get();
$directories = Directory::getChildrenDirectories();
$tags = Tag::get();
$documentsList = Document::query()->where('lang', LangEnum::fr)->get();
$directories = Directory::getChildrenDirectories();
$tags = Tag::get();
return view('bo.pages.documents.create', compact('documents', 'directories', 'tags'));
return view('bo.pages.documents.create', [
'documentModel' => $document,
'documentsList' => $documentsList,
'directories' => $directories,
'tags' => $tags
]);
}
/**
@ -98,14 +112,14 @@ class DocumentController extends Controller
->route('bo.documents.edit', ['tenant' => \tenant(), 'document' => $document])
->with('success', trans(
':model a été créé',
['model' => Str::singular(trans('models.classes.documents'))]
['model' => trans('models.classes.document')]
));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été créé',
['model' => Str::singular(trans('models.classes.documents'))]
['model' => trans('models.classes.document')]
));
});
}
@ -118,13 +132,13 @@ class DocumentController extends Controller
*/
public function edit(Document $document): \Illuminate\View\View
{
$documents = Document::where('lang', LangEnum::fr)->get();
$directories = Directory::getChildrenDirectories();
$tags = Tag::get();
$documentsList = Document::query()->where('lang', LangEnum::fr)->get();
$directories = Directory::getChildrenDirectories();
$tags = Tag::get();
return view('bo.pages.documents.edit', [
'documents' => $documents,
'documentModel' => $document,
'documentsList' => $documentsList,
'directories' => $directories,
'tags' => $tags
]);
@ -147,14 +161,14 @@ class DocumentController extends Controller
->route('bo.documents.edit', ['tenant' => \tenant(), 'document' => $document])
->with('success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.documents'))]
['model' => trans('models.classes.document')]
));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été mis à jour',
['model' => Str::singular(trans('models.classes.documents'))]
['model' => trans('models.classes.document')]
));
});
}
@ -181,14 +195,14 @@ class DocumentController extends Controller
->route('bo.documents.index', array_merge(['tenant' => \tenant()], $previousQueries))
->with('warning', trans(
':model a bien été supprimé',
['model' => Str::singular(trans('models.classes.documents'))]
['model' => trans('models.classes.document')]
));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été supprimé',
['model' => Str::singular(trans('models.classes.documents'))]
['model' => trans('models.classes.document')]
));
}
@ -201,7 +215,7 @@ class DocumentController extends Controller
public function download(string $uuid): \Symfony\Component\HttpFoundation\BinaryFileResponse
{
$doc = Document::where('uuid', $uuid)->firstOrFail();
$pathToFile = public_path() . Storage::url(sprintf("docs/%s.%s", $doc->uuid, $doc->type->name()));
$pathToFile = public_path() . Storage::url(sprintf("docs/%s.%s", $doc->uuid, $doc->type));
return response()->download($pathToFile, $doc->path);
}

View file

@ -2,7 +2,6 @@
namespace App\Http\Controllers\Bo;
use App\Enums\Formularies\InputStatusEnum;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Formularies\StoreFormularyRequest;
use App\Http\Requests\Bo\Formularies\UpdateFormularyRequest;
@ -11,12 +10,19 @@ use App\Models\Formulary;
use App\Traits\Controllers\ChangesModelOrder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class FormularyController extends Controller
{
use ChangesModelOrder;
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(Formulary::class);
}
/**
* Display a listing of the resource.
*
@ -41,19 +47,20 @@ class FormularyController extends Controller
/** Sort columns with a query */
$this->sortQuery($query);
$formulariesModel = $this->paginate($query);
$formularyModels = $this->paginate($query);
return view('bo.pages.formularies.index', compact('formulariesModel', 'searchFields'));
return view('bo.pages.formularies.index', compact('formularyModels', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\Formulary $formulary
* @return \Illuminate\View\View
*/
public function create(): \Illuminate\View\View
public function create(Formulary $formulary): \Illuminate\View\View
{
return view('bo.pages.formularies.create');
return view('bo.pages.formularies.create', ['formularyModel' => $formulary]);
}
/**
@ -73,14 +80,14 @@ class FormularyController extends Controller
->route('bo.formularies.edit', ['tenant' => \tenant(), 'formulary' => $formulary])
->with('success', trans(
':model a été créé',
['model' => Str::singular(trans('models.classes.formularies'))]
['model' => trans('models.classes.formulary')]
));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été créé',
['model' => Str::singular(trans('models.classes.formularies'))]
['model' => trans('models.classes.formulary')]
));
});
}
@ -93,17 +100,9 @@ class FormularyController extends Controller
*/
public function edit(Formulary $formulary): \Illuminate\Http\RedirectResponse|\Illuminate\Contracts\View\View
{
if ($formulary->status->equals(InputStatusEnum::system)) {
return redirect()->route('bo.formularies.index')
->with('error', trans(
'Administration du champs :name impossible car il est vérouillé !',
['name' => $formulary->name]
));
} else {
return view('bo.pages.formularies.edit', [
'formularyModel' => $formulary
]);
}
return view('bo.pages.formularies.edit', [
'formularyModel' => $formulary
]);
}
/**
@ -123,14 +122,14 @@ class FormularyController extends Controller
->route('bo.formularies.edit', ['tenant' => \tenant(), 'formulary' => $formulary])
->with('success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.formularies'))]
['model' => trans('models.classes.formulary')]
));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été mis à jour',
['model' => Str::singular(trans('models.classes.formularies'))]
['model' => trans('models.classes.formulary')]
));
});
}
@ -154,14 +153,25 @@ class FormularyController extends Controller
->route('bo.formularies.index', array_merge(['tenant' => \tenant()], $previousQueries))
->with('warning', trans(
':model a bien été supprimé',
['model' => Str::singular(trans('models.classes.formularies'))]
['model' => trans('models.classes.formulary')]
));
}
return redirect()->back()->with('error', trans(
':model n\'a pas été supprimé',
['model' => Str::singular(trans('models.classes.formularies'))]
['model' => trans('models.classes.formulary')]
));
});
}
/**
* Duplicate the specified resource.
*
* @param \App\Models\Formulary $formulary
* @return \Illuminate\Contracts\View\View
*/
public function duplicate(Formulary $formulary): \Illuminate\Contracts\View\View
{
return $this->create($formulary->replicate());
}
}

View file

@ -6,27 +6,47 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Settings\UpdateSettingRequest;
use App\Models\Directory;
use App\Models\Setting;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class SettingController extends Controller
{
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(Setting::class);
}
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Contracts\View\View
*/
public function index(): \Illuminate\Contracts\View\View
public function index(Request $request): \Illuminate\Contracts\View\View
{
$directories = Directory::renderDirectories(Directory::query()->whereNull('directory_id')->get());
/** @var \Illuminate\Database\Eloquent\Builder $settings */
$settings = Setting::query();
/** @var \Illuminate\Database\Eloquent\Builder $query */
$query = Setting::query();
$this->sortQuery($settings);
$settings = $settings->get();
if ($request->search) {
$this->searchQuery(
$query,
$request->search,
null,
'key',
);
}
$searchFields = trans('validation.custom.key');
return view('bo.pages.settings.index', compact('settings', 'directories'));
$this->sortQuery($query);
$settingModels = $query->get();
return view('bo.pages.settings.index', compact('settingModels', 'searchFields', 'directories'));
}
/**
@ -58,14 +78,14 @@ class SettingController extends Controller
->route('bo.settings.edit', ['tenant' => \tenant(), 'setting' => $setting])
->with('success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.settings'))]
['model' => trans('models.classes.setting')]
));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été mis à jour',
['model' => Str::singular(trans('models.classes.settings'))]
['model' => trans('models.classes.setting')]
));
});
}
@ -81,17 +101,17 @@ class SettingController extends Controller
return DB::transaction(function () use ($setting) {
if ($setting->deleteOrFail()) {
return redirect()
->route('bo.settings.index')
->route('bo.settings.index', ['tenant' => \tenant()])
->with('warning', trans(
':model a bien été supprimé',
['model' => Str::singular(trans('models.classes.settings'))]
['model' => trans('models.classes.setting')]
));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été supprimé',
['model' => Str::singular(trans('models.classes.settings'))]
['model' => trans('models.classes.setting')]
));
});
}

View file

@ -9,12 +9,19 @@ use App\Models\Tag;
use App\Traits\Controllers\ChangesModelOrder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class TagController extends Controller
{
use ChangesModelOrder;
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(Tag::class);
}
/**
* Display a listing of the resource.
*
@ -39,19 +46,20 @@ class TagController extends Controller
/** Sort columns with a query */
$this->sortQuery($query);
$tags = $this->paginate($query);
$tagModels = $this->paginate($query);
return view('bo.pages.tags.index', compact('tags', 'searchFields'));
return view('bo.pages.tags.index', compact('tagModels', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\Tag $tag
* @return \Illuminate\Contracts\View\View
*/
public function create(): \Illuminate\Contracts\View\View
public function create(Tag $tag): \Illuminate\Contracts\View\View
{
return view('bo.pages.tags.create');
return view('bo.pages.tags.create', ['tagModel' => $tag]);
}
/**
@ -70,14 +78,14 @@ class TagController extends Controller
->route('bo.tags.edit', ['tenant' => \tenant(), 'tag' => $tag])
->with('success', trans(
':model a été créé',
['model' => Str::singular(trans('models.classes.tags'))]
['model' => trans('models.classes.tag')]
));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été créé',
['model' => Str::singular(trans('models.classes.tags'))]
['model' => trans('models.classes.tag')]
));
});
}
@ -110,14 +118,14 @@ class TagController extends Controller
->route('bo.tags.edit', ['tenant' => \tenant(), 'tag' => $tag])
->with('success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.tags'))]
['model' => trans('models.classes.tag')]
));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été mis à jour',
['model' => Str::singular(trans('models.classes.tags'))]
['model' => trans('models.classes.tag')]
));
});
}
@ -139,15 +147,26 @@ class TagController extends Controller
->route('bo.tags.index', array_merge(['tenant' => \tenant()], $previousQueries))
->with('warning', trans(
':model a bien été supprimé',
['model' => Str::singular(trans('models.classes.tags'))]
['model' => trans('models.classes.tag')]
));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été supprimé',
['model' => Str::singular(trans('models.classes.tags'))]
['model' => trans('models.classes.tag')]
));
});
}
/**
* Duplicate the specified resource.
*
* @param \App\Models\Tag $tag
* @return \Illuminate\Contracts\View\View
*/
public function duplicate(Tag $tag): \Illuminate\Contracts\View\View
{
return $this->create($tag->replicate());
}
}

View file

@ -3,14 +3,22 @@
namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Tenants\TenantRequest;
use App\Http\Requests\Bo\Tenants\StoreTenantRequest;
use App\Http\Requests\Bo\Tenants\UpdateTenantRequest;
use App\Models\Tenant;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class TenantController extends Controller
{
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(Tenant::class);
}
/**
* Display a listing of the resource.
*
@ -35,28 +43,29 @@ class TenantController extends Controller
/** Sort columns with a query */
$this->sortQuery($query);
$tenants = $this->paginate($query);
$tenantModels = $this->paginate($query);
return view('bo.pages.tenants.index', compact('tenants', 'searchFields'));
return view('bo.pages.tenants.index', compact('tenantModels', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\Tenant $tenant
* @return \Illuminate\Contracts\View\View
*/
public function create(): \Illuminate\Contracts\View\View
public function create(Tenant $tenant): \Illuminate\Contracts\View\View
{
return view('bo.pages.tenants.create');
return view('bo.pages.tenants.create', ['tenantModel' => $tenant]);
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Tenants\TenantRequest $request
* @param \App\Http\Requests\Bo\Tenants\StoreTenantRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(TenantRequest $request): \Illuminate\Http\RedirectResponse
public function store(StoreTenantRequest $request): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request) {
$tenant = new Tenant();
@ -66,14 +75,14 @@ class TenantController extends Controller
->route('bo.tenants.edit', ['tenant' => $tenant])
->with('success', trans(
':model a été créé',
['model' => Str::singular(trans('models.classes.tenants'))]
['model' => trans('models.classes.tenant')]
));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été créé',
['model' => Str::singular(trans('models.classes.tenants'))]
['model' => trans('models.classes.tenant')]
));
});
}
@ -92,11 +101,11 @@ class TenantController extends Controller
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Tenants\TenantRequest $request
* @param \App\Models\Tenant $tenant
* @param \App\Http\Requests\Bo\Tenants\UpdateTenantRequest $request
* @param \App\Models\Tenant $tenant
* @return \Illuminate\Http\RedirectResponse
*/
public function update(TenantRequest $request, Tenant $tenant): \Illuminate\Http\RedirectResponse
public function update(UpdateTenantRequest $request, Tenant $tenant): \Illuminate\Http\RedirectResponse
{
return DB::transaction(function () use ($request, $tenant) {
$tenant->fill($request->validated());
@ -106,14 +115,14 @@ class TenantController extends Controller
->route('bo.tenants.edit', ['tenant' => $tenant])
->with('success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.tenants'))]
['model' => trans('models.classes.tenant')]
));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été mis à jour',
['model' => Str::singular(trans('models.classes.tenants'))]
['model' => trans('models.classes.tenant')]
));
});
}
@ -132,18 +141,29 @@ class TenantController extends Controller
return DB::transaction(function () use ($tenant, $previousQueries) {
if ($tenant->deleteOrFail()) {
return redirect()
->route('bo.tenants.index', array_merge(['tenant' => \tenant()], $previousQueries))
->route('bo.tenants.index', $previousQueries)
->with('warning', trans(
':model a bien été supprimé',
['model' => Str::singular(trans('models.classes.tenants'))]
['model' => trans('models.classes.tenant')]
));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été supprimé',
['model' => Str::singular(trans('models.classes.tenants'))]
['model' => trans('models.classes.tenant')]
));
});
}
/**
* Duplicate the specified resource.
*
* @param \App\Models\Tenant $tenant
* @return \Illuminate\Contracts\View\View
*/
public function duplicate(Tenant $tenant): \Illuminate\Contracts\View\View
{
return $this->create($tenant->replicate());
}
}

View file

@ -5,6 +5,7 @@ namespace App\Http\Controllers\Bo;
use App\Http\Controllers\Controller;
use App\Http\Requests\Bo\Users\StoreUserRequest;
use App\Http\Requests\Bo\Users\UpdateUserRequest;
use App\Models\Tenant;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
@ -12,6 +13,14 @@ use Illuminate\Support\Str;
class UserController extends Controller
{
/**
* Create the controller instance.
*/
public function __construct()
{
$this->authorizeResource(User::class);
}
/**
* Display a listing of the resource.
*
@ -20,35 +29,48 @@ class UserController extends Controller
*/
public function index(Request $request): \Illuminate\Contracts\View\View
{
/** @var \Illuminate\Database\Eloquent\Builder $query */
$query = User::query();
/** @var \Illuminate\Database\Eloquent\Builder $userModels */
$userModels = User::query();
if ($request->search) {
/** @var string $search Search field */
$search = $request->search;
if ($search) {
$this->searchQuery(
$query,
$request->search,
$userModels,
$search,
null,
'name',
'first_name',
'last_name',
'email',
'role',
);
}
$searchFields = trans('validation.attributes.name');
$searchFields = \implode(', ', [
trans('validation.attributes.first_name'),
trans('validation.attributes.last_name'),
trans('validation.attributes.email'),
trans('validation.attributes.role'),
]);
/** Sort columns with a query */
$this->sortQuery($query);
$this->sortQuery($userModels);
$users = $this->paginate($query);
/** Custom pagination */
$userModels = $this->paginate($userModels);
return view('bo.pages.users.index', compact('users', 'searchFields'));
return view('bo.pages.users.index', compact('userModels', 'search', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @param \App\Models\User $user
* @return \Illuminate\Contracts\View\View
*/
public function create(): \Illuminate\Contracts\View\View
public function create(User $user): \Illuminate\Contracts\View\View
{
return view('bo.pages.users.create');
$tenants = Tenant::query()->orderBy('name', 'ASC')->get();
return view('bo.pages.users.create', ['userModel' => $user, 'tenants' => $tenants]);
}
/**
@ -62,20 +84,18 @@ class UserController extends Controller
return DB::transaction(function () use ($request) {
$user = new User();
$user->fill($request->validated());
if ($user->saveOrFail()) {
return redirect()
->route('bo.users.edit', ['tenant' => \tenant(), 'user' => $user])
->with('success', trans(
':model a été créé',
['model' => Str::singular(trans('models.classes.users'))]
));
$user->tenants()->sync(array_column($request->validated()['tenants'], 'id'));
return redirect()->route('bo.users.edit', ['tenant' => tenant(), 'user' => $user])
->with('success', __('crud.messages.has_been_created', [
'model' => Str::ucfirst(__('models.classes.user'))
]));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été créé',
['model' => Str::singular(trans('models.classes.users'))]
));
return redirect()->back()
->with('error', __('crud.messages.cannot_be_created', [
'model' => Str::ucfirst(__('models.classes.user'))
]));
});
}
@ -87,7 +107,8 @@ class UserController extends Controller
*/
public function edit(User $user): \Illuminate\Contracts\View\View
{
return view('bo.pages.users.edit', ['userModel' => $user]);
$tenants = Tenant::query()->orderBy('name', 'ASC')->get();
return view('bo.pages.users.edit', ['userModel' => $user, 'tenants' => $tenants]);
}
/**
@ -103,19 +124,16 @@ class UserController extends Controller
$user->fill($request->validated());
if ($user->saveOrFail()) {
return redirect()
->route('bo.users.edit', ['tenant' => \tenant(), 'user' => $user])
->with('success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.users'))]
));
$user->tenants()->sync(array_column($request->validated()['tenants'], 'id'));
return redirect()->route('bo.users.edit', ['tenant' => tenant(), 'user' => $user])
->with('success', __('crud.messages.has_been_updated', [
'model' => Str::ucfirst(__('models.classes.user'))
]));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été mis à jour',
['model' => Str::singular(trans('models.classes.users'))]
));
return redirect()->route('bo.users.edit', ['tenant' => tenant(), 'user' => $user])
->with('error', __('crud.messages.cannot_be_updated', [
'model' => Str::ucfirst(__('models.classes.user'))
]));
});
}
@ -127,24 +145,26 @@ class UserController extends Controller
*/
public function destroy(User $user): \Illuminate\Http\RedirectResponse
{
/** @var array<string,string> */
$previousQueries = [];
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
return DB::transaction(function () use ($user, $previousQueries) {
if ($user->deleteOrFail()) {
return redirect()
->route('bo.users.index', array_merge(['tenant' => \tenant(), 'user' => $user], $previousQueries))
->with('warning', trans(
':model a bien été supprimé',
['model' => Str::singular(trans('models.classes.users'))]
));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été supprimé',
['model' => Str::singular(trans('models.classes.users'))]
));
});
if ($user->deleteOrFail()) {
return redirect()->route('bo.users.index', ['tenant' => tenant()])
->with('success', __('crud.messages.has_been_deleted', [
'model' => Str::ucfirst(__('models.classes.user'))
]));
}
return redirect()->back()
->with('error', __('crud.messages.cannot_be_deleted', [
'model' => Str::ucfirst(__('models.classes.user'))
]));
}
/**
* Duplicate the specified resource.
*
* @param \App\Models\User $user
* @return \Illuminate\Contracts\View\View
*/
public function duplicate(User $user): \Illuminate\Contracts\View\View
{
return $this->create($user->replicate());
}
}

View file

@ -2,9 +2,7 @@
namespace App\Http\Controllers;
use App\Enums\UserRolesEnum;
use App\Lib\Helpers\ToolboxHelper;
use App\Models\Tenant;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
@ -166,20 +164,4 @@ class Controller extends BaseController
$query = $query->paginate($pagination);
return $query;
}
/**
* Redirect with default tenant.
*
* @return \Illuminate\Http\RedirectResponse
*/
public function redirectWithDefaultTenant(): \Illuminate\Http\RedirectResponse
{
$user = auth('backend')->user();
$tenant = UserRolesEnum::admin->equals($user->role) ?
Tenant::first()->getKey() :
$user->tenants->first()->getKey();
return redirect()->route('bo.dashboard', [
'tenant' => $tenant
]);
}
}

View file

@ -33,7 +33,7 @@ class RedirectIfAuthenticated
case 'frontend':
return \redirect()->route('home');
case 'backend':
return \redirect()->route('bo.dashboard', ['tenant' => tenant()]);
return \redirect()->route('bo.dashboard');
default:
throw new \RuntimeException("Unhandled guard `{$guard}` .");
}

View file

@ -2,7 +2,6 @@
namespace App\Http\Middleware;
use App\Enums\UserRolesEnum;
use App\Models\Tenant;
use Closure;
use Illuminate\Http\Request;
@ -19,19 +18,36 @@ class RestrictTenantAccess
*/
public function handle(Request $request, Closure $next): \Symfony\Component\HttpFoundation\Response
{
/** @var \App\Models\User|null $user */
$user = \auth('backend')->user();
if ($user) {
if ($request->routeIs('bo.tenants.*')) {
tenancy()->initialize(Tenant::firstOrFail());
if (
!$request->routeIs('bo.dashboard') &&
!$request->routeIs('bo.users.*') &&
!$request->routeIs('bo.tenants.*') &&
$request->tenant &&
!\in_array($request->tenant, Tenant::get()->pluck('id')->toArray())
) {
// @phpstan-ignore argument.type
abort('404');
}
if (
(Tenant::count() && $request->routeIs('bo.tenants.*') && $user->can('isAdmin')) ||
(Tenant::count() && $request->routeIs('bo.users.*') && $user->can('isAdmin'))
) {
$tenant = Tenant::first()->getKey();
tenancy()->initialize($tenant);
return $next($request);
}
if (
!\in_array($request->tenant, $user->tenants->pluck('id')->toArray()) &&
!(UserRolesEnum::admin->equals($user->role))
Tenant::count() && ($request->routeIs('bo.dashboard')
|| $request->routeIs('bo.users.edit', $user->getKey()))
) {
abort(403);
$tenant = ($user->tenants->count()) ? $user->tenants->first() : Tenant::first()->getKey();
tenancy()->initialize($tenant);
return $next($request);
}
}
} //end if
return $next($request);
}
}

View file

@ -41,7 +41,7 @@ class SendFlyerRequest extends FormRequest
'email' => trans('validation.attributes.email'),
'subject' => trans('validation.attributes.subject'),
'message' => trans('validation.attributes.message'),
'app_user_id' => trans('models.classes.app_users'),
'app_user_id' => trans('models.classes.app_user'),
];
}
}

View file

@ -3,7 +3,10 @@
namespace App\Http\Requests\Bo\AppUser;
use App\Enums\AppUserRolesEnum;
use App\Models\AppUser;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Enum;
use Illuminate\Validation\Rules\Password;
@ -17,7 +20,7 @@ class StoreAppUserRequest extends FormRequest
*/
public function authorize(): bool
{
return auth('backend')->check();
return Gate::check('create', AppUser::class);
}
/**
@ -76,13 +79,13 @@ class StoreAppUserRequest extends FormRequest
public function attributes(): array
{
return [
'lastname' => \trans('nom'),
'firstname' => \trans('prénom'),
'email' => \trans('email'),
'password' => \trans('mot de passe'),
'authorizedFolders' => \trans('dossiers autorisés'),
'role' => \trans('rôle'),
'status' => \trans('statut')
'lastname' => \trans('validation.attributes.last_name'),
'firstname' => \trans('validation.attributes.first_name'),
'email' => \trans('validation.attributes.email'),
'password' => \trans('validation.attributes.password'),
'authorizedFolders' => Str::of(\trans('models.classes.directory'))->plural(),
'role' => \trans('validation.custom.role'),
'status' => \trans('validation.custom.status')
];
}
}

View file

@ -2,11 +2,22 @@
namespace App\Http\Requests\Bo\AppUser;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Password;
class UpdateAppUserRequest extends StoreAppUserRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('update', $this->route('app_user'));
}
/**
* Get the validation rules that apply to the request.
*

View file

@ -3,7 +3,9 @@
namespace App\Http\Requests\Bo\Directories;
use App\Enums\Documents\LangEnum;
use App\Models\Directory;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
class StoreDirectoryRequest extends FormRequest
{
@ -14,7 +16,7 @@ class StoreDirectoryRequest extends FormRequest
*/
public function authorize(): bool
{
return auth('backend')->check();
return Gate::check('create', Directory::class);
}
/**
@ -51,14 +53,12 @@ class StoreDirectoryRequest extends FormRequest
public function attributes(): array
{
$rules = [
'name' => \trans('nom du dossier'),
'needAuthorization' => \trans('autorisation du dossier')
'name' => \trans('validation.attributes.name'),
'needAuthorization' => \trans('validation.custom.authorization')
];
/** @var Object $langEnum */
foreach (LangEnum::toArray() as $langEnum) {
$rules["name.{$langEnum->name}"] = \trans('nom du dossier :lang', [
'lang' => $langEnum->label
]);
$rules["name.{$langEnum->name}"] = sprintf('%s %s', trans('validation.attributes.name'), $langEnum->label);
}
return $rules;
}

View file

@ -2,6 +2,17 @@
namespace App\Http\Requests\Bo\Directories;
use Illuminate\Support\Facades\Gate;
class UpdateDirectoryRequest extends StoreDirectoryRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('update', $this->route('directory'));
}
}

View file

@ -7,7 +7,9 @@ use App\Enums\Documents\TypeEnum;
use App\Lib\Helpers\FileStorageHelper;
use App\Models\Document;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Enum;
class StoreDocumentRequest extends FormRequest
@ -19,7 +21,7 @@ class StoreDocumentRequest extends FormRequest
*/
public function authorize(): bool
{
return auth('backend')->check();
return Gate::check('create', Document::class);
}
/**
@ -30,7 +32,7 @@ class StoreDocumentRequest extends FormRequest
protected function prepareForValidation(): void
{
$this->merge([
'status' => $this->status ? true : false
'status' => $this->status ? true : false,
]);
// * Sanitize Path
if (!empty($this->path)) {
@ -66,10 +68,12 @@ class StoreDocumentRequest extends FormRequest
'directory_id' => 'required|integer',
'document_id' => 'sometimes:directory_id|nullable|integer',
'uuid' => [
'required',
'uuid',
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
'required', 'uuid', function ($attribute, $value, $fail) use ($disk) {
if ($this->type and \in_array($this->type, TypeEnum::toValues())) {
$extension = TypeEnum::make($this->type, 'value')->name();
function ($attribute, $value, $fail) use ($disk) {
if ($this->type and \array_column(TypeEnum::toArray(), 'name')) {
$extension = TypeEnum::make($this->type, 'name')->name();
$value = "{$value}.{$extension}";
}
if (!$disk->has($value)) {
@ -81,15 +85,9 @@ class StoreDocumentRequest extends FormRequest
}
],
'title' => 'required|min:3|max:255',
'type' => [
'required',
new Enum(TypeEnum::class)
],
'type' => ['required', 'string', Rule::in(array_column(TypeEnum::toArray(), 'name'))],
'path' => 'required|min:3|max:255',
'lang' => [
'required',
new Enum(LangEnum::class)
],
'lang' => ['required', new Enum(LangEnum::class)],
'status' => 'required|boolean'
];
}
@ -102,14 +100,14 @@ class StoreDocumentRequest extends FormRequest
public function attributes(): array
{
return [
'directory_id' => \trans('dossier à associer'),
'document_id' => \trans('document (FR) à associer'),
'title' => \trans('titre'),
'uuid' => \trans('document'),
'type' => \trans('type du document'),
'path' => \trans('chemin du document'),
'lang' => \trans('langue du document'),
'status' => \trans('état de publication du document'),
'directory_id' => \trans('models.classes.directory'),
'document_id' => \trans('models.classes.document'),
'title' => \trans('validation.attributes.title'),
'uuid' => \trans('models.classes.document'),
'type' => \trans('validation.custom.type'),
'path' => \trans('validation.custom.path'),
'lang' => \trans('validation.custom.lang'),
'status' => \trans('validation.custom.status'),
];
}
}

View file

@ -2,6 +2,17 @@
namespace App\Http\Requests\Bo\Documents;
use Illuminate\Support\Facades\Gate;
class UpdateDocumentRequest extends StoreDocumentRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('update', $this->route('document'));
}
}

View file

@ -14,7 +14,7 @@ class FilterCustomerRequest extends FormRequest
*/
public function authorize(): bool
{
return \auth()->check();
return \auth('backend')->check();
}
/**

View file

@ -4,7 +4,9 @@ namespace App\Http\Requests\Bo\Formularies;
use App\Enums\Formularies\InputStatusEnum;
use App\Enums\Formularies\InputTypeEnum;
use App\Models\Formulary;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rules\Enum;
class StoreFormularyRequest extends FormRequest
@ -15,6 +17,7 @@ class StoreFormularyRequest extends FormRequest
* @var string[]
*/
private $mobileAppLocales = ['fr', 'en', 'de', 'nl'];
/**
* Determine if the user is authorized to make this request.
*
@ -22,7 +25,7 @@ class StoreFormularyRequest extends FormRequest
*/
public function authorize(): bool
{
return auth('backend')->check();
return Gate::check('create', Formulary::class);
}
/**
@ -71,14 +74,14 @@ class StoreFormularyRequest extends FormRequest
public function attributes(): array
{
$attributes = [
'type' => trans('type'),
'name' => trans('nom'),
'placeholder' => trans('placeholder'),
'details' => trans('détail'),
'details.*' => trans('détail 2'),
'details.*.value' => trans('valeur d\'un élément dans la liste des détails du contenu'),
'required' => trans('l\'état d\'obligation'),
'is_visible' => trans('l\'état de visibilité'),
'type' => trans('validation.custom.type'),
'name' => trans('validation.attributes.name'),
'placeholder' => trans('validation.custom.placeholder'),
'details' => trans('validation.custom.detail'),
'details.*' => trans('validation.custom.detail'),
'details.*.value' => trans('validation.custom.valeur'),
'required' => trans('validation.custom.required'),
'is_visible' => trans('validation.custom.visibility'),
];
foreach ($this->mobileAppLocales as $locale) {
$attributes["placeholder.{$locale}"] = trans('placeholder (:lang)', ['lang' => $locale]);

View file

@ -2,8 +2,20 @@
namespace App\Http\Requests\Bo\Formularies;
use Illuminate\Support\Facades\Gate;
class UpdateFormularyRequest extends StoreFormularyRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('update', $this->route('formulary'));
}
/**
* Get the validation rules that apply to the request.
*

View file

@ -2,11 +2,11 @@
namespace App\Http\Requests\Bo\Settings;
use App\Enums\UserRolesEnum;
use App\Models\Setting;
use App\Traits\Requests\HasPicture;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
class UpdateSettingRequest extends FormRequest
{
@ -25,9 +25,11 @@ class UpdateSettingRequest extends FormRequest
$this->key === "Button3" ||
$this->key === "ShowIsActive"
) {
return auth('backend')->check();
return Gate::check('update', $this->route('setting'));
} else {
return (auth('backend')->check() && Auth::user()->role->value() === UserRolesEnum::admin->value());
/** @var \App\Models\User $user */
$user = Auth::guard('backend')->user();
return $user->can('isAdmin');
}
}
@ -70,9 +72,9 @@ class UpdateSettingRequest extends FormRequest
) {
$buttonsNullable = 0;
$buttons = Setting::where('key', 'Button1')
->orWhere('key', 'Button2')
->orWhere('key', 'Button3')
->get();
->orWhere('key', 'Button2')
->orWhere('key', 'Button3')
->get();
foreach ($buttons as $button) {
if ($button->value === null) {
if ($button->key !== $this->key) {
@ -110,9 +112,9 @@ class UpdateSettingRequest extends FormRequest
public function attributes(): array
{
return [
'key' => \trans('clé'),
'value' => \trans('valeur'),
'type' => \trans('type'),
'key' => \trans('validation.custom.key'),
'value' => \trans('validation.custom.value'),
'type' => \trans('validation.custom.type'),
];
}
}

View file

@ -2,8 +2,11 @@
namespace App\Http\Requests\Bo\Tags;
use App\Models\Tag;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
class StoreTagRequest extends FormRequest
{
@ -14,7 +17,7 @@ class StoreTagRequest extends FormRequest
*/
public function authorize(): bool
{
return auth('backend')->check();
return Gate::check('create', Tag::class);
}
/**
@ -38,6 +41,11 @@ class StoreTagRequest extends FormRequest
{
return [
'name' => 'required|string|min:2|max:25',
'slug' => [
'required',
'string',
Rule::unique('tags', 'slug')->where('tenant_id', tenant('id'))
]
];
}
@ -49,8 +57,8 @@ class StoreTagRequest extends FormRequest
public function attributes(): array
{
return [
'name' => trans('name of the tag'),
'slug' => trans('slug of the tag'),
'name' => trans('models.classes.tag'),
'slug' => trans('validation.custom.slug'),
];
}
}

View file

@ -2,6 +2,17 @@
namespace App\Http\Requests\Bo\Tags;
use Illuminate\Support\Facades\Gate;
class UpdateTagRequest extends StoreTagRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('update', $this->route('tag'));
}
}

View file

@ -7,7 +7,7 @@ use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Str;
class TenantRequest extends FormRequest
class StoreTenantRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.

View file

@ -0,0 +1,18 @@
<?php
namespace App\Http\Requests\Bo\Tenants;
use Illuminate\Support\Facades\Gate;
class UpdateTenantRequest extends StoreTenantRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('update', $this->route('tenant'));
}
}

View file

@ -13,7 +13,7 @@ class UploadRequest extends FormRequest
*/
public function authorize(): bool
{
return \auth()->check();
return \auth('backend')->check();
}
/**

View file

@ -0,0 +1,61 @@
<?php
namespace App\Http\Requests\Bo\Users;
use App\Enums\UserRolesEnum;
use App\Models\User;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Enum;
class StoreUserRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('create', User::class);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'first_name' => 'required|string|min:3|max:255',
'last_name' => 'required|string|min:3|max:255',
'email' => [
'required',
'email:rfc,strict,dns,spoof,filter',
Rule::unique('users', 'email')
],
'role' => ['required', new Enum(UserRolesEnum::class)],
'tenants' => 'required|nullable|array|min:1',
'tenants.*' => 'required|nullable|array|min:1',
'tenants.*.id' => 'required|nullable|exists:tenants,id',
'tenants.*.*' => 'required|nullable|string|min:1',
];
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{
return [
'first_name' => trans('validation.attributes.first_name'),
'last_name' => trans('validation.attributes.last_name'),
'email' => trans('validation.attributes.email'),
'role' => trans('validation.attributes.role'),
];
}
}

View file

@ -0,0 +1,42 @@
<?php
namespace App\Http\Requests\Bo\Users;
use App\Enums\UserRolesEnum;
use Illuminate\Support\Facades\Gate;
use Illuminate\Validation\Rules\Enum;
class UpdateUserRequest extends StoreUserRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize(): bool
{
return Gate::check('update', $this->route('user'));
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
/** @var \App\Models\User $userModel */
$userModel = request()->route()->parameter('user');
$rules = [
'email' => [
'required',
'string',
'unique:users,email,' . $userModel->getKey(),
'email:rfc,strict,dns,spoof,filter',
'max:255'
],
'role' => ['sometimes', new Enum(UserRolesEnum::class)],
];
return \array_merge(parent::rules(), $rules);
}
}

View file

@ -25,7 +25,7 @@ class DocumentResource extends JsonResource
'id' => $document->id,
'uuid' => $document->uuid,
'title' => $document->title,
'type' => $document->type->value,
'type' => $document->type,
'directory' => $document->directory_id,
'path' => $document->path,
'lang' => $document->lang->value,

View file

@ -279,7 +279,7 @@ class ToolboxHelper
* @param string $table
* @param string $field
* @param string|integer|float|boolean|null|array $value
* @param integer|null $id
* @param integer|string|null $id
* @param callable|null $query
* @return void
* @throws \Illuminate\Validation\ValidationException If the field is not unique.
@ -287,8 +287,8 @@ class ToolboxHelper
public static function assertFieldIsUnique(
string $table,
string $field,
string|int|float|bool|null|array $value,
?int $id = null,
string|int|float|bool|array|null $value,
int|string|null $id = null,
?callable $query = null
) {
if (!$query) {

View file

@ -15,9 +15,9 @@ use Stancl\Tenancy\Database\Concerns\BelongsToTenant;
* @property string $firstname App user first name.
* @property string $email App user email.
* @property string $password App user password.
* @property integer $tenant_id Tenant key associated.
* @property \App\Enums\AppUserRolesEnum $role App user role.
* @property string|null $tenant_id Associated tenant.
* @property boolean $status App user status.
* @property \App\Enums\AppUserRolesEnum $role App user role.
* @property integer $order App user order.
* @property-read \Carbon\Carbon $created_at Created date.
* @property-read \Carbon\Carbon $updated_at Updated date.
@ -116,7 +116,9 @@ class AppUser extends Model
*/
private static function setOrder(self $model): void
{
$model->order = \intval(self::query()->max('order')) + 1;
$model->order = \intval(
self::query()->where('tenant_id', \tenant() ?? Tenant::first()->getKey())->max('order')
) + 1;
}
// * RELATIONSHIPS

View file

@ -10,6 +10,7 @@ use Stancl\Tenancy\Database\Concerns\BelongsToTenant;
/**
* @property integer $id Directory id.
* @property array<string,string> $name Directory name.
* @property string|null $tenant_id Associated tenant.
* @property integer|null $directory_id Associated directory.
* @property integer $order Directory order.
* @property boolean $needAuthorization Directory authorization.
@ -73,7 +74,9 @@ class Directory extends Model
*/
private static function setOrder(self $model): void
{
$model->order = \intval(self::query()->max('order')) + 1;
$model->order = \intval(
self::query()->where('tenant_id', \tenant() ?? Tenant::first()->getKey())->max('order')
) + 1;
}
/**

View file

@ -3,7 +3,6 @@
namespace App\Models;
use App\Enums\Documents\LangEnum;
use App\Enums\Documents\TypeEnum;
use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
@ -14,9 +13,10 @@ use Stancl\Tenancy\Database\Concerns\BelongsToTenant;
* @property integer $id Document id.
* @property integer $uuid Document uuid.
* @property string $title Document title.
* @property \App\Enums\Documents\TypeEnum $type Document type.
* @property string $type Document type.
* @property string|null $tenant_id Associated tenant.
* @property integer $directory_id Associated directory.
* @property integer $document_id Associated document.
* @property integer|null $document_id Associated document.
* @property string $path Document path.
* @property \App\Enums\Documents\LangEnum $lang Document lang.
* @property boolean $status Document status.
@ -54,7 +54,6 @@ class Document extends Model
protected $casts = [
'status' => 'bool',
'lang' => LangEnum::class,
'type' => TypeEnum::class,
];
/**
@ -103,7 +102,9 @@ class Document extends Model
*/
private static function setOrder(self $model): void
{
$model->order = \intval(self::query()->max('order')) + 1;
$model->order = \intval(
self::query()->where('tenant_id', \tenant() ?? Tenant::first()->getKey())->max('order')
) + 1;
}
/**
@ -114,7 +115,7 @@ class Document extends Model
*/
public static function removeDocument(self $model): void
{
$path = \ltrim(\urldecode("storage/docs/{$model->uuid}.{$model->type->label()}"), '/');
$path = \ltrim(\urldecode("storage/docs/{$model->uuid}.{$model->type}"), '/');
if (\strpos($path, 'storage') !== 0) {
return;
}

View file

@ -10,18 +10,19 @@ use Illuminate\Support\Str;
use Stancl\Tenancy\Database\Concerns\BelongsToTenant;
/**
* @property integer $id Formulary id.
* @property \App\Enums\Formularies\InputTypeEnum $type Formulary type.
* @property string $name Formulary name.
* @property string $slug Formulary slug.
* @property array<string,string> $placeholder Formulary placeholders.
* @property array<string,array<string,string>> $details Formuary details.
* @property boolean $is_visible Formulary visibility.
* @property boolean $required Formulary required status.
* @property \App\Enums\Formularies\InputStatusEnum $status Formulary status.
* @property integer $order Formulary order.
* @property-read \Carbon\Carbon $created_at Created date.
* @property-read \Carbon\Carbon $updated_at Updated date.
* @property integer $id Formulary id.
* @property \App\Enums\Formularies\InputTypeEnum $type Formulary type.
* @property string $name Formulary name.
* @property string $slug Formulary slug.
* @property array<string,string> $placeholder Formulary placeholders.
* @property array<string,array<string,string>>|null $details Formuary details.
* @property string|null $tenant_id Associated tenant.
* @property boolean $is_visible Formulary visibility.
* @property boolean $required Formulary required status.
* @property \App\Enums\Formularies\InputStatusEnum $status Formulary status.
* @property integer $order Formulary order.
* @property-read \Carbon\Carbon $created_at Created date.
* @property-read \Carbon\Carbon $updated_at Updated date.
*/
class Formulary extends Model
{
@ -125,6 +126,71 @@ class Formulary extends Model
*/
private static function setOrder(self $model): void
{
$model->order = \intval(self::query()->max('order')) + 1;
$model->order = \intval(
self::query()->where('tenant_id', \tenant() ?? Tenant::first()->getKey())->max('order')
) + 1;
}
/**
* Set default settings for a specific tenant.
*
* @param \App\Models\Tenant $tenant
* @return void
*/
public function setDefaultFormularies(Tenant $tenant): void
{
// @phpcs:disable Generic.Files.LineLength.TooLong
$array = [
[
'name' => 'e-mail',
'placeholder' => ['fr' => 'E-mail'],
'details' => null,
'type' => InputTypeEnum::email,
'status' => InputStatusEnum::system,
'is_visible' => true,
'required' => true,
],
[
'name' => 'firstname',
'placeholder' => ['fr' => 'Prénom'],
'details' => null,
'type' => InputTypeEnum::text,
'status' => InputStatusEnum::locked,
'is_visible' => true,
'required' => true,
],
[
'name' => 'lastname',
'placeholder' => ['fr' => 'Nom'],
'details' => null,
'type' => InputTypeEnum::text,
'status' => InputStatusEnum::locked,
'is_visible' => true,
'required' => true,
],
[
'name' => 'phone',
'placeholder' => ['fr' => 'Téléphone'],
'details' => null,
'type' => InputTypeEnum::phone,
'status' => InputStatusEnum::locked,
'is_visible' => true,
'required' => true,
],
];
// @phpcs:enable
collect($array)->map(function (array $item) use ($tenant) {
$setting = new self();
$setting->name = $item['name'];
$setting->slug = Str::of($item['name'])->slug();
$setting->placeholder = $item['placeholder'];
$setting->details = $item['details'];
$setting->type = $item['type'];
$setting->status = $item['status'];
$setting->is_visible = $item['is_visible'];
$setting->required = $item['required'];
$setting->tenant_id = $tenant->getKey();
$setting->saveOrFail();
});
}
}

View file

@ -12,6 +12,7 @@ use Stancl\Tenancy\Database\Concerns\BelongsToTenant;
* @property integer $id Settings id.
* @property string $key Settings key.
* @property string $value Settings value.
* @property string|null $tenant_id Associated tenant.
* @property \App\Enums\SettingTypesEnum $type Settings type.
* @property-read \Carbon\Carbon $created_at Created date.
* @property-read \Carbon\Carbon $updated_at Updated date.
@ -52,6 +53,16 @@ class Setting extends Model
*/
protected static function booted(): void
{
static::creating(function (self $model) {
if (
$model->key === "SquaredLogo" ||
$model->key === "RectangleLogo" ||
$model->key === "BackgroundImage" ||
$model->key === "HomeButtonImage"
) {
$model->value = FileStorageHelper::storeFile($model, $model->value, true);
}
});
static::updating(function (self $model) {
if (
$model->key === "SquaredLogo" ||
@ -73,4 +84,42 @@ class Setting extends Model
}
});
}
// * METHODS
/**
* Set default settings for a specific tenant.
*
* @param \App\Models\Tenant $tenant
* @return void
*/
public function setDefaultSettings(Tenant $tenant): void
{
// @phpcs:disable Generic.Files.LineLength.TooLong
$array = [
['key' => 'version', 'value' => '1', 'type' => SettingTypesEnum::numeric],
['key' => 'ShowIsActive', 'value' => '0', 'type' => SettingTypesEnum::boolean],
['key' => 'MainColor', 'value' => '#FFF', 'type' => SettingTypesEnum::color],
['key' => 'SquaredLogo', 'value' => new \SplFileInfo(\resource_path('assets/settings/SquaredLogo.png')), 'type' => SettingTypesEnum::picture],
['key' => 'RectangleLogo', 'value' => new \SplFileInfo(\resource_path('assets/settings/RectangleLogo.png')), 'type' => SettingTypesEnum::picture],
['key' => 'BackgroundImage', 'value' => new \SplFileInfo(\resource_path('assets/settings/BackgroundImage.png')), 'type' => SettingTypesEnum::picture],
['key' => 'HomeButtonImage', 'value' => new \SplFileInfo(\resource_path('assets/settings/HomeButtonImage.png')), 'type' => SettingTypesEnum::picture],
['key' => 'Button1', 'value' => null, 'type' => SettingTypesEnum::directory],
['key' => 'Button2', 'value' => null, 'type' => SettingTypesEnum::directory],
['key' => 'Button3', 'value' => null, 'type' => SettingTypesEnum::directory],
['key' => 'AppStoreUUID', 'value' => 'c4ac09fd-3992-47bc-bdee-0b7f5505e873', 'type' => SettingTypesEnum::string],
['key' => 'FormSelect1', 'value' => '{"title":{"fr":"Travaille avec","en":"Work with","de":"Arbeit mit"},"values":{"pole-sable":{"fr":"P\u00f4le Sabl\u00e9","en":"P\u00f4le Sabl\u00e9","de":"P\u00f4le Sabl\u00e9"},"snv":{"fr":"SNV","en":"SNV","de":"SNV"},"pole-sud-est":{"fr":"P\u00f4le Sud-Est","en":"P\u00f4le Sud-Est","de":"P\u00f4le Sud-Est"},"aquitaine":{"fr":"Aquitaine","en":"Aquitaine","de":"Aquitaine"},"sbv":{"fr":"SBV","en":"SBV","de":"SBV"},"arrive":{"fr":"Arriv\u00e9","en":"Arriv\u00e9","de":"Arriv\u00e9"},"plusieurs-filiales":{"fr":"Plusieurs filiales","en":"Several subsidiaries","de":"Mehrere Filialen"},"autre":{"fr":"Autre","en":"Other","de":"Andere"}}}', 'type' => SettingTypesEnum::json],
['key' => 'FormSelect2', 'value' => '{"title":{"fr":"Type de commerce","en":"Type of business","de":"Art des Gesch\u00e4fts"},"values":{"commerce-de-detail":{"fr":"Commerce de d\u00e9tail","en":"Retail trade","de":"Einzelhandel"},"restauration-commerciale":{"fr":"Restauration Commerciale","en":"Commercial catering","de":"Kommerzielle Restauration"},"restauration-collective":{"fr":"Restauration Collective","en":"Collective catering","de":"Kollektiv Restauration"},"grossiste-distributeur-cash":{"fr":"Grossiste Distributeur Cash","en":"Wholesale Distributor Cash","de":"Gro\u00dfh\u00e4ndler H\u00e4ndler Cash"},"pai":{"fr":"PAI","en":"PAI","de":"PAI"},"home-freezer-center":{"fr":"Home Freezer Center","en":"Home Freezer Center","de":"Home Gefrierzentrum"},"autres":{"fr":"Autre","en":"Other","de":"Andere"}}}', 'type' => SettingTypesEnum::json],
['key' => 'FormCheck', 'value' => '[{"fr":"Frais","en":"Fresh","de":"Frisch"},{"fr":"Surgel\u00e9s","en":"Frozen food","de":"Tiefk\u00fchlkost"},{"fr":"Volailles crues","en":"Raw poultry","de":"Rohes Gefl\u00fcgel"},{"fr":"\u00c9labor\u00e9s crus","en":"Prepared raw","de":"Roh ausgearbeitet"},{"fr":"\u00c9labor\u00e9s cuits","en":"Cooked products","de":"Gekochte Elaborate"},{"fr":"Bio et Label Rouge","en":"Bio and Label Rouge","de":"Bio und Label Rouge"},{"fr":"BBC","en":"BBC","de":"BBC"},{"fr":"Halal","en":"Halal","de":"Halal"},{"fr":"Autre","en":"Other","de":"Andere"}]', 'type' => SettingTypesEnum::json],
];
// @phpcs:enable
collect($array)->map(function (array $item) use ($tenant) {
$setting = new self();
$setting->key = $item['key'];
$setting->value = $item['value'];
$setting->type = $item['type'];
$setting->tenant_id = $tenant->getKey();
$setting->saveOrFail();
});
}
}

View file

@ -2,16 +2,15 @@
namespace App\Models;
use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Stancl\Tenancy\Database\Concerns\BelongsToTenant;
/**
* @property integer $id Tag id.
* @property string $slug Tag slug.
* @property string $name Tag name.
* @property string|null $tenant_id Associated tenant.
* @property integer $order Tag order.
* @property-read \Carbon\Carbon $created_at Created date.
* @property-read \Carbon\Carbon $updated_at Updated date.
@ -39,13 +38,8 @@ class Tag extends Model
protected static function booted(): void
{
static::creating(function (self $tag) {
self::setSlug($tag);
self::assertFieldIsUnique($tag->slug);
self::setOrder($tag);
});
static::updating(function (self $tag) {
self::assertFieldIsUnique($tag->slug, $tag->id);
});
static::deleting(function (self $tag) {
$tag->documents()->detach();
});
@ -53,32 +47,6 @@ class Tag extends Model
// * METHODS
/**
* Set the slug.
*
* @param \App\Models\Tag $tag
*
* @return void
*/
private static function setSlug(Tag $tag): void
{
$tag->slug = Str::slug($tag->name);
}
/**
* Asserts using validation that the field is unique.
*
* @param string $slug
* @param integer|null $id
* @return void
* @throws \Illuminate\Validation\ValidationException If field already exists.
*/
private static function assertFieldIsUnique(string $slug, ?int $id = null): void
{
$table = (new self())->getTable();
ToolboxHelper::assertFieldIsUnique($table, 'slug', $slug, $id);
}
/**
* Set order after the last element of the list.
*
@ -87,7 +55,9 @@ class Tag extends Model
*/
private static function setOrder(Tag $tag): void
{
$tag->order = \intval(self::query()->max('order')) + 1;
$tag->order = \intval(
self::query()->where('tenant_id', \tenant() ?? Tenant::first()->getKey())->max('order')
) + 1;
}
// * RELATIONSHIPS

View file

@ -40,17 +40,15 @@ class Tenant extends BaseTenant implements TenantContract
protected static function booted(): void
{
static::creating(function (self $model) {
// * ASSERTIONS
self::assertNameIsUnique($model->name);
// * TRANSFORMATIONS
$model->id = Str::slug($model->name);
});
static::created(function (self $model) {
(new Setting())->setDefaultSettings($model);
(new Formulary())->setDefaultFormularies($model);
});
static::updating(function (self $model) {
// * ASSERTIONS
self::assertNameIsUnique($model->name, $model->id);
// * TRANSFORMATIONS
self::assertNameIsUnique($model->name, $model->getKey());
$model->id = Str::slug($model->name);
});
}
@ -60,15 +58,67 @@ class Tenant extends BaseTenant implements TenantContract
/**
* Asserts using validation that the name is unique
*
* @param string|null $name
* @param integer|null $id
* @param string|null $name
* @param string|null $id
* @return void
* @throws \Illuminate\Validation\ValidationException If name already exists.
*/
private static function assertNameIsUnique(string|null $name, ?int $id = null): void
private static function assertNameIsUnique(string|null $name, ?string $id = null): void
{
$table = (new self())->getTable();
ToolboxHelper::assertFieldIsUnique($table, 'name', $name, $id);
ToolboxHelper::assertFieldIsUnique($table, 'id', Str::slug($name), $id);
}
// * RELATIONSHIPS
/**
* Has Many Relationship Method.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function appUsers(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(AppUser::class);
}
/**
* Has Many Relationship Method.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function directories(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(Directory::class);
}
/**
* Has Many Relationship Method.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function documents(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(Document::class);
}
/**
* Has Many Relationship Method.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function formularies(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(Formulary::class);
}
/**
* Has Many Relationship Method.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function settings(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(Setting::class);
}
}

View file

@ -5,25 +5,19 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;
use App\Enums\UserRolesEnum;
use Stancl\Tenancy\Database\Concerns\BelongsToTenant;
/**
* @property integer $firstname User first name.
* @property string $lastname User last name.
* @property integer $first_name User first name.
* @property string $last_name User last name.
* @property string $email User email.
* @property string $email_verified_at Email verified date.
* @property string $password User password.
* @property string $remember_token User token remember.
* @property integer $tenant_id Tenant key associated.
* @property \App\Enums\UserRolesEnum $role User role.
* @property \Carbon\Carbon $last_connection Last connection date.
* @property-read \Carbon\Carbon $created_at Created date.
* @property-read \Carbon\Carbon $updated_at Updated date.
*/
class User extends Authenticatable
{
use BelongsToTenant;
use HasFactory;
use Notifiable;
@ -33,65 +27,23 @@ class User extends Authenticatable
* @var list<string>
*/
protected $fillable = [
'firstname',
'lastname',
'first_name',
'last_name',
'email',
'email_verified_at',
'password',
'last_connection',
'role',
];
/**
* The attributes that should be hidden for serialization.
*
* @var list<string>
*/
protected $hidden = [
'password',
'remember_token',
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'email_verified_at' => 'datetime',
'role' => UserRolesEnum::class
'last_connection' => 'datetime',
'role' => UserRolesEnum::class
];
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted(): void
{
// * EVENT CLOSURES.
static::creating(function (self $model) {
self::updatePassword($model);
});
static::updating(function (self $model) {
self::updatePassword($model);
});
}
// * METHODS
/**
* Update password.
*
* @param self $model
* @return void
*/
private static function updatePassword(self $model): void
{
if (Hash::needsRehash($model->password)) {
$model->password = Hash::make($model->password);
}
}
// * RELATIONSHIPS
/**
@ -103,4 +55,14 @@ class User extends Authenticatable
{
return $this->morphToMany(Directory::class, 'directorables');
}
/**
* The tenants that belong to the user.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function tenants(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->belongsToMany(Tenant::class);
}
}

View file

@ -2,20 +2,85 @@
namespace App\Policies;
use App\Enums\UserRolesEnum;
use App\Models\User;
use App\Policies\Rules\UserStaticRules;
class AppUserPolicy
{
/**
* Determine whether the user can publish/unpublish the model.
* Determine whether the user can view any models.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function viewAny(User $authUser): bool
{
return UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can view the model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function view(User $authUser): bool
{
return UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can create models.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function create(User $authUser): bool
{
return UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can update the model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function update(User $authUser): bool
{
return UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can delete the model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function delete(User $authUser): bool
{
return UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can change order of the model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function changeOrder(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, UserRolesEnum::admin);
return UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can duplicate the model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function duplicate(User $authUser): bool
{
return UserStaticRules::restrictTenant($authUser);
}
}

View file

@ -0,0 +1,75 @@
<?php
namespace App\Policies;
use App\Models\User;
use App\Policies\Rules\UserStaticRules;
class ContactPolicy
{
/**
* Determine whether the user can view any models.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function viewAny(User $authUser): bool
{
return UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can view the model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function view(User $authUser): bool
{
return UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can create models.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function create(User $authUser): bool
{
return UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can update the model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function update(User $authUser): bool
{
return UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can delete the model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function delete(User $authUser): bool
{
return UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can change order of the model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function changeOrder(User $authUser): bool
{
return UserStaticRules::restrictTenant($authUser);
}
}

View file

@ -0,0 +1,78 @@
<?php
namespace App\Policies;
use App\Models\Directory;
use App\Models\User;
use App\Policies\Rules\UserStaticRules;
class DirectoryPolicy
{
/**
* Determine whether the user can view any models.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function viewAny(User $authUser): bool
{
return UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can view the model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function view(User $authUser): bool
{
return UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can create models.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function create(User $authUser): bool
{
return UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can update the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\Directory $directory
* @return boolean
*/
public function update(User $authUser, Directory $directory): bool
{
return !$directory->is_system && UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can delete the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\Directory $directory
* @return boolean
*/
public function delete(User $authUser, Directory $directory): bool
{
return !$directory->is_system && UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can change order of the model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function changeOrder(User $authUser): bool
{
return UserStaticRules::restrictTenant($authUser);
}
}

View file

@ -2,20 +2,74 @@
namespace App\Policies;
use App\Enums\UserRolesEnum;
use App\Models\User;
use App\Policies\Rules\UserStaticRules;
class DocumentPolicy
{
/**
* Determine whether the user can publish/unpublish the model.
* Determine whether the user can view any models.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function viewAny(User $authUser): bool
{
return UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can view the model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function view(User $authUser): bool
{
return UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can create models.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function create(User $authUser): bool
{
return UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can update the model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function update(User $authUser): bool
{
return UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can delete the model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function delete(User $authUser): bool
{
return UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can change order of the model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function changeOrder(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, UserRolesEnum::admin);
return UserStaticRules::restrictTenant($authUser);
}
}

View file

@ -2,20 +2,97 @@
namespace App\Policies;
use App\Enums\UserRolesEnum;
use App\Enums\Formularies\InputStatusEnum;
use App\Models\Formulary;
use App\Models\User;
use App\Policies\Rules\UserStaticRules;
class FormularyPolicy
{
/**
* Determine whether the user can publish/unpublish the model.
* Determine whether the user can view any models.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function viewAny(User $authUser): bool
{
return UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can view the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\Formulary $formulary
* @return boolean
*/
public function view(User $authUser, Formulary $formulary): bool
{
return !$formulary->status->equals(InputStatusEnum::system) &&
UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can create models.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function create(User $authUser): bool
{
return UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can update the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\Formulary $formulary
* @return boolean
*/
public function update(User $authUser, Formulary $formulary): bool
{
return !$formulary->status->equals(InputStatusEnum::system) &&
UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can delete the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\Formulary $formulary
* @return boolean
*/
public function delete(User $authUser, Formulary $formulary): bool
{
return !$formulary->status->equals(InputStatusEnum::system) &&
!$formulary->status->equals(InputStatusEnum::locked) &&
UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can duplicate the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\Formulary $formulary
* @return boolean
*/
public function duplicate(User $authUser, Formulary $formulary): bool
{
return !$formulary->status->equals(InputStatusEnum::system) &&
!$formulary->status->equals(InputStatusEnum::locked) &&
UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can change order of the model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function changeOrder(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, UserRolesEnum::admin);
return UserStaticRules::restrictTenant($authUser);
}
}

View file

@ -3,6 +3,7 @@
namespace App\Policies\Rules;
use App\Enums\UserRolesEnum;
use App\Models\User;
use Illuminate\Database\Eloquent\Model;
/**
@ -41,7 +42,19 @@ abstract class UserStaticRules
*/
public static function atLeastRole(Model $authModel, UserRolesEnum $minRole): bool
{
// phpcs:enable
return isset($authModel->role) && $authModel->role->value() <= $minRole->value();
}
/**
* Allow client users associated to the tenant OR admin users.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public static function restrictTenant(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, UserRolesEnum::client) &&
\in_array(\tenant()->getKey(), $authUser->tenants->pluck('id')->toArray()) ||
UserStaticRules::atLeastRole($authUser, UserRolesEnum::admin);
}
}

View file

@ -0,0 +1,42 @@
<?php
namespace App\Policies;
use App\Models\User;
use App\Policies\Rules\UserStaticRules;
class SettingPolicy
{
/**
* Determine whether the user can view any models.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function viewAny(User $authUser): bool
{
return UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can view the model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function view(User $authUser): bool
{
return UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can update the model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function update(User $authUser): bool
{
return UserStaticRules::restrictTenant($authUser);
}
}

View file

@ -2,20 +2,85 @@
namespace App\Policies;
use App\Enums\UserRolesEnum;
use App\Models\User;
use App\Policies\Rules\UserStaticRules;
class TagPolicy
{
/**
* Determine whether the user can publish/unpublish the model.
* Determine whether the user can view any models.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function viewAny(User $authUser): bool
{
return UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can view the model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function view(User $authUser): bool
{
return UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can create models.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function create(User $authUser): bool
{
return UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can update the model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function update(User $authUser): bool
{
return UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can delete the model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function delete(User $authUser): bool
{
return UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can duplicate the model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function duplicate(User $authUser): bool
{
return UserStaticRules::restrictTenant($authUser);
}
/**
* Determine whether the user can change order of the model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function changeOrder(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, UserRolesEnum::admin);
return UserStaticRules::restrictTenant($authUser);
}
}

View file

@ -3,7 +3,6 @@
namespace App\Policies;
use App\Enums\UserRolesEnum;
use App\Models\Tenant;
use App\Models\User;
use App\Policies\Rules\UserStaticRules;
@ -19,6 +18,18 @@ class TenantPolicy
{
return UserStaticRules::atLeastRole($authUser, UserRolesEnum::admin);
}
/**
* Determine whether the user can view the model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function view(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, UserRolesEnum::admin);
}
/**
* Determine whether the user can create models.
*
@ -30,17 +41,36 @@ class TenantPolicy
return UserStaticRules::atLeastRole($authUser, UserRolesEnum::admin);
}
/**
* Determine whether the user can update the model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function update(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, UserRolesEnum::admin);
}
/**
* Determine whether the user can delete the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\Tenant $tenant
* @param \App\Models\User $authUser
* @return boolean
* @phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed
*/
public function delete(User $authUser, Tenant $tenant): bool
public function delete(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, UserRolesEnum::admin);
}
/**
* Determine whether the user can duplicate the model.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function duplicate(User $authUser): bool
{
// phpcs:enable
return UserStaticRules::atLeastRole($authUser, UserRolesEnum::admin);
}
}

View file

@ -0,0 +1,88 @@
<?php
namespace App\Policies;
use App\Enums\UserRolesEnum;
use App\Models\User;
use App\Policies\Rules\UserStaticRules;
class UserPolicy
{
/**
* Determine whether the user can view any models.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function viewAny(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, UserRolesEnum::admin);
}
/**
* Determine whether the user can view the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\User $user
* @return boolean
*/
public function view(User $authUser, User $user): bool
{
return $authUser->getRouteKey() === $user->getRouteKey() ||
UserStaticRules::atLeastRole($authUser, UserRolesEnum::admin);
}
/**
* Determine whether the user can create models.
*
* @param \App\Models\User $authUser
* @return boolean
*/
public function create(User $authUser): bool
{
return UserStaticRules::atLeastRole($authUser, UserRolesEnum::admin);
}
/**
* Determine whether the user can update the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\User $user
* @return boolean
*/
public function update(User $authUser, User $user): bool
{
return (
UserStaticRules::atLeastRole($authUser, UserRolesEnum::client) &&
$authUser->getRouteKey() === $user->getRouteKey()
) || UserStaticRules::atLeastRole($authUser, UserRolesEnum::admin);
}
/**
* Determine whether the user can delete the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\User $user
* @return boolean
*/
public function delete(User $authUser, User $user): bool
{
return $authUser->getRouteKey() !== $user->getRouteKey() &&
UserStaticRules::atLeastRole($authUser, UserRolesEnum::admin);
}
/**
* Determine whether the user can duplicate the model.
*
* @param \App\Models\User $authUser
* @param \App\Models\User $user
* @return boolean
*/
public function duplicate(User $authUser, User $user): bool
{
return (
UserStaticRules::atLeastRole($authUser, UserRolesEnum::client) &&
$authUser->getRouteKey() === $user->getRouteKey()
) || UserStaticRules::atLeastRole($authUser, UserRolesEnum::admin);
}
}

View file

@ -3,7 +3,6 @@
namespace App\Providers;
use App\Enums\UserRolesEnum;
use App\Models\Directory;
use App\Models\User;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
@ -19,14 +18,6 @@ class AuthServiceProvider extends ServiceProvider
{
$this->registerPolicies();
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
Gate::define('edit', function (User $user, Directory $directory) {
return !$directory->is_system;
});
// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed
Gate::define('delete', function (User $user, Directory $directory) {
return !$directory->is_system;
});
Gate::define('isAdmin', function (User $user) {
return $user->role->equals(UserRolesEnum::admin);
});

View file

@ -15,26 +15,32 @@ use Stancl\Tenancy\Middleware;
class TenancyServiceProvider extends ServiceProvider
{
// By default, no namespace is used to support the callable array syntax.
/**
* By default, no namespace is used to support the callable array syntax.
*
* @var string
*/
public static string $controllerNamespace = '';
public function events()
/** @return array */
public function events(): array
{
return [
// Tenant events
// Tenant events.
Events\CreatingTenant::class => [],
Events\TenantCreated::class => [
JobPipeline::make([
Jobs\CreateDatabase::class,
Jobs\MigrateDatabase::class,
// Jobs\SeedDatabase::class,
// Your own jobs to prepare the tenant.
// Provision API keys, create S3 buckets, anything you want!
])->send(function (Events\TenantCreated $event) {
return $event->tenant;
})->shouldBeQueued(false), // `false` by default, but you probably want to make this `true` for production.
// Provision API keys, create S3 buckets, anything you want !
])
->send(function (Events\TenantCreated $event) {
return $event->tenant;
})
// `false` by default, but you probably want to make this `true` for production.
->shouldBeQueued(false),
],
Events\SavingTenant::class => [],
Events\TenantSaved::class => [],
@ -44,12 +50,15 @@ class TenancyServiceProvider extends ServiceProvider
Events\TenantDeleted::class => [
JobPipeline::make([
Jobs\DeleteDatabase::class,
])->send(function (Events\TenantDeleted $event) {
return $event->tenant;
})->shouldBeQueued(false), // `false` by default, but you probably want to make this `true` for production.
])
->send(function (Events\TenantDeleted $event) {
return $event->tenant;
})
// `false` by default, but you probably want to make this `true` for production.
->shouldBeQueued(false),
],
// Domain events
// Domain events.
Events\CreatingDomain::class => [],
Events\DomainCreated::class => [],
Events\SavingDomain::class => [],
@ -59,14 +68,14 @@ class TenancyServiceProvider extends ServiceProvider
Events\DeletingDomain::class => [],
Events\DomainDeleted::class => [],
// Database events
// Database events.
Events\DatabaseCreated::class => [],
Events\DatabaseMigrated::class => [],
Events\DatabaseSeeded::class => [],
Events\DatabaseRolledBack::class => [],
Events\DatabaseDeleted::class => [],
// Tenancy events
// Tenancy events.
Events\InitializingTenancy::class => [],
Events\TenancyInitialized::class => [
Listeners\BootstrapTenancy::class,
@ -82,22 +91,25 @@ class TenancyServiceProvider extends ServiceProvider
Events\RevertingToCentralContext::class => [],
Events\RevertedToCentralContext::class => [],
// Resource syncing
// Resource syncing.
Events\SyncedResourceSaved::class => [
Listeners\UpdateSyncedResource::class,
],
// Fired only when a synced resource is changed in a different DB than the origin DB (to avoid infinite loops)
// Fired only when a synced resource is changed in a different DB
// than the origin DB (to avoid infinite loops).
Events\SyncedResourceChangedInForeignDatabase::class => [],
];
}
public function register()
/** @return void */
public function register(): void
{
//
// Silence is golden.
}
public function boot()
/** @return void */
public function boot(): void
{
$this->bootEvents();
$this->mapRoutes();
@ -105,7 +117,8 @@ class TenancyServiceProvider extends ServiceProvider
$this->makeTenancyMiddlewareHighestPriority();
}
protected function bootEvents()
/** @return void */
protected function bootEvents(): void
{
foreach ($this->events() as $event => $listeners) {
foreach ($listeners as $listener) {
@ -118,7 +131,8 @@ class TenancyServiceProvider extends ServiceProvider
}
}
protected function mapRoutes()
/** @return void */
protected function mapRoutes(): void
{
$this->app->booted(function () {
if (file_exists(base_path('routes/tenant.php'))) {
@ -128,10 +142,11 @@ class TenancyServiceProvider extends ServiceProvider
});
}
protected function makeTenancyMiddlewareHighestPriority()
/** @return void */
protected function makeTenancyMiddlewareHighestPriority(): void
{
$tenancyMiddleware = [
// Even higher priority than the initialization middleware
// Even higher priority than the initialization middleware.
Middleware\PreventAccessFromCentralDomains::class,
Middleware\InitializeTenancyByDomain::class,

View file

@ -4,8 +4,9 @@ namespace App\Providers;
use App\Models\Tenant;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
class ViewServiceProvider extends ServiceProvider
{
@ -25,7 +26,8 @@ class ViewServiceProvider extends ServiceProvider
Paginator::useBootstrap();
}
}
View::share('tenantModels', Tenant::get());
if (Schema::hasTable('tenants')) {
View::share('tenantsGlobal', Tenant::get());
}
}
}

View file

@ -23,7 +23,7 @@ trait ChangesModelOrder
$paramNames = collect(request()->route()->parameterNames);
$className = Str::of($paramNames->get($paramNames->count() - 2))->lower()->studly();
$className = "\\App\\Models\\{$className}";
$model = Arr::get($params, $paramNames->count() - 2);
$model = Arr::get($params, $paramNames->count() - 3);
if (!class_exists($className)) {
throw new \RuntimeException("The {$className} class doesn't exist.");
@ -39,7 +39,7 @@ trait ChangesModelOrder
/** @var \Illuminate\Database\Eloquent\Model|null */
$model = $className::query()
->where('tenant_id', \tenant())
->where('tenant_id', \tenant()->getKey())
->where($model->getRouteKeyName(), $model->getRouteKey())
->first();
@ -50,7 +50,7 @@ trait ChangesModelOrder
return DB::transaction(function () use ($request, $className, $model) {
/** @var \Illuminate\Database\Eloquent\Model|null */
$tmp = $className::query()
->where('tenant_id', \tenant())
->where('tenant_id', \tenant()->getKey())
->where('order', $request->direction ? '<' : '>', $model->getOriginal('order'))
->orderBy('order', $request->direction ? 'DESC' : 'ASC')->first();

77
config/breadcrumbs.php Normal file
View file

@ -0,0 +1,77 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| View Name
|--------------------------------------------------------------------------
|
| Choose a view to display when Breadcrumbs::render() is called.
| Built in templates are:
|
| - 'breadcrumbs::bootstrap5' - Bootstrap 5
| - 'breadcrumbs::bootstrap4' - Bootstrap 4
| - 'breadcrumbs::bulma' - Bulma
| - 'breadcrumbs::foundation6' - Foundation 6
| - 'breadcrumbs::json-ld' - JSON-LD Structured Data
| - 'breadcrumbs::materialize' - Materialize
| - 'breadcrumbs::tailwind' - Tailwind CSS
| - 'breadcrumbs::uikit' - UIkit
|
| Or a custom view, e.g. '_partials/breadcrumbs'.
|
*/
'view' => 'modules/breadcrumbs/breadcrumb-layout',
/*
|--------------------------------------------------------------------------
| Breadcrumbs File(s)
|--------------------------------------------------------------------------
|
| The file(s) where breadcrumbs are defined. e.g.
|
| - base_path('routes/breadcrumbs.php')
| - glob(base_path('breadcrumbs/*.php'))
|
*/
'files' => [
base_path('routes/breadcrumbs-bo.php'),
],
/*
|--------------------------------------------------------------------------
| Exceptions
|--------------------------------------------------------------------------
|
| Determine when to throw an exception.
|
*/
// * When route-bound breadcrumbs are used but the current route doesn't have a name (UnnamedRouteException)
'unnamed-route-exception' => true,
// * When route-bound breadcrumbs are used and the matching breadcrumb doesn't exist (InvalidBreadcrumbException)
'missing-route-bound-breadcrumb-exception' => true,
// * When a named breadcrumb is used but doesn't exist (InvalidBreadcrumbException)
'invalid-named-breadcrumb-exception' => true,
/*
|--------------------------------------------------------------------------
| Classes
|--------------------------------------------------------------------------
|
| Subclass the default classes for more advanced customisations.
|
*/
// * Manager
'manager-class' => Diglactic\Breadcrumbs\Manager::class,
// * Generator
'generator-class' => Diglactic\Breadcrumbs\Generator::class,
];

View file

@ -29,7 +29,6 @@ return [
// * For iframes
'URI.SafeIframeRegexp' => "%^(http://|https://|//)(www.youtube.com/embed/|player.vimeo.com/video/|vimeo.com/)%",
'HTML.Doctype' => 'HTML 4.01 Transitional',
// TODO: Rules en FULL CKEDITOR.
// * Legacy filter en full avec safe legacy.
// 'HTML.Allowed' => 'h1[style],h2[style],h3[style],h4[style],h5[style],h6[style],figure[class|style],oembed[url],div[style],b,strong,i,em,u,a[href|title|target|rel],ul[style],ol[style],dl[style],li,p[style],br,span[style|class],img[width|height|alt|src|style|class|title],iframe[src|width|height|class|frameborder|style],table[style|align|border|cellspacing|cellpadding|width],thead,tbody,th[colspan|align|valign|style],tr[align|valign|style],td[style|colspan|rowspan|scope|align|valign],hr,dl,dt',
// * Restricted basics.

View file

@ -1,5 +1,7 @@
<?php
// phpcs:disable Squiz.PHP.CommentedOutCode.Found,Squiz.Commenting.InlineComment.InvalidEndChar
declare(strict_types=1);
use Stancl\Tenancy\Database\Models\Domain;
@ -70,7 +72,8 @@ return [
* Disable the pgsql manager above, and enable the one below if you
* want to separate tenant DBs by schemas rather than databases.
*/
// 'pgsql' => Stancl\Tenancy\TenantDatabaseManagers\PostgreSQLSchemaManager::class, // Separate by schema instead of database
// Separate by schema instead of database
// 'pgsql' => Stancl\Tenancy\TenantDatabaseManagers\PostgreSQLSchemaManager::class,
],
],
@ -86,7 +89,8 @@ return [
* You can clear cache selectively by specifying the tag.
*/
'cache' => [
'tag_base' => 'tenant', // This tag_base, followed by the tenant_id, will form a tag that will be applied on each cache call.
// This tag_base, followed by the tenant_id, will form a tag that will be applied on each cache call.
'tag_base' => 'tenant',
],
/**
@ -147,8 +151,10 @@ return [
* either using the Redis facade or by injecting it as a dependency.
*/
'redis' => [
'prefix_base' => 'tenant', // Each key in Redis will be prepended by this prefix_base, followed by the tenant id.
'prefixed_connections' => [ // Redis connections whose keys are prefixed, to separate one tenant's keys from another.
// Each key in Redis will be prepended by this prefix_base, followed by the tenant id.
'prefix_base' => 'tenant',
// Redis connections whose keys are prefixed, to separate one tenant's keys from another.
'prefixed_connections' => [
// 'default',
],
],
@ -165,8 +171,10 @@ return [
// Stancl\Tenancy\Features\UserImpersonation::class,
// Stancl\Tenancy\Features\TelescopeTags::class,
// Stancl\Tenancy\Features\UniversalRoutes::class,
// Stancl\Tenancy\Features\TenantConfig::class, // https://tenancyforlaravel.com/docs/v3/features/tenant-config
// Stancl\Tenancy\Features\CrossDomainRedirect::class, // https://tenancyforlaravel.com/docs/v3/features/cross-domain-redirect
// https://tenancyforlaravel.com/docs/v3/features/tenant-config
// Stancl\Tenancy\Features\TenantConfig::class,
// https://tenancyforlaravel.com/docs/v3/features/cross-domain-redirect
// Stancl\Tenancy\Features\CrossDomainRedirect::class,
// Stancl\Tenancy\Features\ViteBundler::class,
],
@ -183,7 +191,8 @@ return [
* Parameters used by the tenants:migrate command.
*/
'migration_parameters' => [
'--force' => true, // This needs to be true to run migrations in production.
// This needs to be true to run migrations in production.
'--force' => true,
'--path' => [database_path('migrations/tenant')],
'--realpath' => true,
],
@ -192,7 +201,9 @@ return [
* Parameters used by the tenants:seed command.
*/
'seeder_parameters' => [
'--class' => 'DatabaseSeeder', // root seeder class
// '--force' => true, // This needs to be true to seed tenant databases in production
// Root seeder class.
'--class' => 'DatabaseSeeder',
// This needs to be true to seed tenant databases in production.
// '--force' => true,
],
];

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -2,5 +2,5 @@
return [
'home' => 'Home',
'admin' => 'Administration',
'admin' => 'Avanced',
];

View file

@ -2,14 +2,14 @@
return [
'classes' => [
'app_users' => 'Application\'s users',
'directories' => 'Directories',
'documents' => 'Documents',
'users' => 'Users',
'settings' => 'Settings',
'tags' => 'Tags',
'formularies' => 'Formularies',
'contacts' => 'Contacts',
'tenants' => 'Tenants',
'app_user' => 'App. user',
'directory' => 'Directory',
'document' => 'Document',
'user' => 'User',
'setting' => 'Setting',
'tag' => 'Tag',
'formulary' => 'Formulary',
'contact' => 'Contact',
'tenant' => 'Tenant',
]
];

View file

@ -125,77 +125,93 @@ return [
'url' => 'The :attribute format is invalid.',
'uuid' => 'The :attribute must be a valid UUID.',
'attributes' => [
'address' => 'address',
'age' => 'age',
'amount' => 'amount',
'area' => 'area',
'available' => 'available',
'birthday' => 'birthday',
'body' => 'body',
'city' => 'city',
'content' => 'content',
'country' => 'country',
'created_at' => 'created at',
'creator' => 'creator',
'current_password' => 'current password',
'date' => 'date',
'date_of_birth' => 'date of birth',
'day' => 'day',
'deleted_at' => 'deleted at',
'description' => 'description',
'district' => 'district',
'duration' => 'duration',
'email' => 'email address',
'excerpt' => 'excerpt',
'filter' => 'filter',
'first_name' => 'first name',
'gender' => 'gender',
'group' => 'group',
'hour' => 'hour',
'image' => 'image',
'last_name' => 'last name',
'lesson' => 'lesson',
'line_address_1' => 'address line 1',
'line_address_2' => 'address line 2',
'message' => 'message',
'middle_name' => 'middle name',
'minute' => 'minute',
'mobile' => 'mobile',
'month' => 'month',
'name' => 'name',
'national_code' => 'national code',
'number' => 'number',
'password' => 'password',
'password_confirmation' => 'password confirmation',
'phone' => 'phone',
'photo' => 'photo',
'postal_code' => 'postal code',
'price' => 'price',
'province' => 'province',
'address' => 'address',
'age' => 'age',
'amount' => 'amount',
'area' => 'area',
'available' => 'available',
'birthday' => 'birthday',
'body' => 'body',
'city' => 'city',
'content' => 'content',
'country' => 'country',
'created_at' => 'created at',
'creator' => 'creator',
'current_password' => 'current password',
'date' => 'date',
'date_of_birth' => 'date of birth',
'day' => 'day',
'deleted_at' => 'deleted at',
'description' => 'description',
'district' => 'district',
'duration' => 'duration',
'email' => 'email address',
'excerpt' => 'excerpt',
'filter' => 'filter',
'first_name' => 'first name',
'gender' => 'gender',
'group' => 'group',
'hour' => 'hour',
'image' => 'image',
'last_name' => 'last name',
'lesson' => 'lesson',
'line_address_1' => 'address line 1',
'line_address_2' => 'address line 2',
'message' => 'message',
'middle_name' => 'middle name',
'minute' => 'minute',
'mobile' => 'mobile',
'month' => 'month',
'name' => 'name',
'national_code' => 'national code',
'number' => 'number',
'password' => 'password',
'password_confirmation' => 'password confirmation',
'phone' => 'phone',
'photo' => 'photo',
'postal_code' => 'postal code',
'price' => 'price',
'province' => 'province',
'recaptcha_response_field' => 'recaptcha response field',
'remember' => 'remember',
'restored_at' => 'restored at',
'result_text_under_image' => 'result text under image',
'role' => 'role',
'second' => 'second',
'sex' => 'sex',
'short_text' => 'short text',
'size' => 'size',
'state' => 'state',
'street' => 'street',
'student' => 'student',
'subject' => 'subject',
'teacher' => 'teacher',
'terms' => 'terms',
'test_description' => 'test description',
'test_locale' => 'test locale',
'test_name' => 'test name',
'text' => 'text',
'time' => 'time',
'title' => 'title',
'updated_at' => 'updated at',
'username' => 'username',
'year' => 'year',
'zipcode' => 'zipcode',
'remember' => 'remember',
'restored_at' => 'restored at',
'result_text_under_image' => 'result text under image',
'role' => 'role',
'second' => 'second',
'sex' => 'sex',
'short_text' => 'short text',
'size' => 'size',
'state' => 'state',
'street' => 'street',
'student' => 'student',
'subject' => 'subject',
'teacher' => 'teacher',
'terms' => 'terms',
'test_description' => 'test description',
'test_locale' => 'test locale',
'test_name' => 'test name',
'text' => 'text',
'time' => 'time',
'title' => 'title',
'updated_at' => 'updated at',
'username' => 'username',
'year' => 'year',
'zipcode' => 'zipcode',
],
'custom' => [
'authorization' => 'authorization',
'detail' => 'detail',
'key' => 'key',
'lang' => 'lang',
'order' => 'order',
'path' => 'path',
'placeholder' => 'placeholder',
'required' => 'required',
'role' => 'role',
'slug' => 'slug',
'status' => 'status',
'type' => 'type',
'value' => 'value',
'visibility' => 'visibility',
]
];

View file

@ -2,5 +2,5 @@
return [
'home' => 'Accueil',
'admin' => 'Administration',
'admin' => 'Avancé',
];

View file

@ -2,14 +2,14 @@
return [
'classes' => [
'app_users' => 'Application utilisateurs',
'directories' => 'Dossiers',
'documents' => 'Documents',
'users' => 'Utilisateurs',
'settings' => 'Paramètres',
'tags' => 'Étiquettes',
'formularies' => 'Formulaires',
'contacts' => 'Contacts',
'tenants' => 'Tenants',
'app_user' => 'App. utilisateur',
'directory' => 'Dossier',
'document' => 'Document',
'user' => 'Utilisateur',
'setting' => 'Paramètre',
'tag' => 'Étiquette',
'formulary' => 'Formulaire',
'contact' => 'Contact',
'tenant' => 'Tenant',
]
];

View file

@ -199,4 +199,20 @@ return [
'year' => 'année',
'zipcode' => 'code postal'
],
'custom' => [
'authorization' => 'autorisation',
'detail' => 'détail',
'key' => 'clé',
'lang' => 'langue',
'order' => 'ordre',
'path' => 'chemin',
'placeholder' => 'placeholder',
'required' => 'requis',
'role' => 'rôle',
'slug' => 'slug',
'status' => 'statut',
'type' => 'type',
'value' => 'valeur',
'visibility' => 'visibilité',
]
];

View file

@ -1,16 +1,23 @@
.need-word-counter {
position: relative;
border-radius: 0 0 0.375rem 0.375rem;
// * LAYOUT
&-password {
border-radius: 0 0 0 0.375rem !important;
body {
.navbar-brand > .logo {
max-width: 9rem;
}
&-btn {
border-radius: 0 0 0.375rem !important;
}
> main {
min-height: calc(100vh - (#{$bo-header-height} + #{$bo-footer-height}));
margin-top: $bo-header-height;
}
> footer {
height: $bo-footer-height;
box-shadow: 0 -0.125rem 0.25rem rgb(0 0 0 / 7.5%) !important;
}
}
// * OTHERS
.table {
&.fix-action {
th:last-child,
@ -39,25 +46,33 @@
max-height: 5rem;
}
.w-fit {
width: fit-content;
}
.btn-md {
height: 38px;
}
.btn-sm {
height: 31px;
}
.pagination-custom .dropdown-menu {
overflow: auto;
min-width: 5rem;
max-height: 178px;
}
.btn-md {
min-width: 38px;
height: 38px;
}
.need-word-counter {
position: relative;
border-radius: 0 0 0.375rem 0.375rem;
.w-fit {
width: fit-content;
}
&-password {
border-radius: 0 0 0 0.375rem !important;
.table-col-sorter {
height: 3rem;
}
main {
min-height: calc(100vh - 56px - 40px);
&-btn {
border-radius: 0 0 0.375rem !important;
}
}
}

View file

@ -15,3 +15,7 @@ $font-default-family-base: system-ui, -apple-system, 'Segoe UI', 'Roboto',
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; // - The default site font family.
$font-default-size-base: 1rem; // Assumes the browser default, typically `16px`
$line-default-height-base: 1.5;
// * DIMENSIONS
$bo-header-height: 4.8rem;
$bo-footer-height: 2rem;

View file

@ -17,6 +17,7 @@
@import "bootstrap/scss/reboot";
@import "bootstrap/scss/accordion";
@import "bootstrap/scss/alert";
@import "bootstrap/scss/badge";
@import "bootstrap/scss/breadcrumb";
@import "bootstrap/scss/button-group";
@import "bootstrap/scss/buttons";

View file

@ -273,10 +273,14 @@ onMounted(() => {
intDirectories.value = data.directories;
intModel.value = data.model;
if (intModel.value != null) {
intDocumentDatas.value.path = String(intModel.value.path);
intDocumentDatas.value.uuid = String(intModel.value.uuid);
intDocumentDatas.value.type = String(intModel.value.type);
intIsDocFR.value = intModel.value.lang === 1 ? true : false;
if (intModel.value.lang === undefined) {
intIsDocFR.value = true;
} else {
intDocumentDatas.value.path = String(intModel.value.path);
intDocumentDatas.value.uuid = String(intModel.value.uuid);
intDocumentDatas.value.type = String(intModel.value.type);
intIsDocFR.value = intModel.value.lang === 1 ? true : false;
}
}
intCsrf.value = data.csrf;
nextTick(() => {

View file

@ -37,7 +37,7 @@
}"
>
<vue-nestable-handle
class="d-flex justify-content-between align-items-center border rounded px-2 py-1"
class="d-flex justify-content-between align-items-center border rounded p-1"
>
<div class="d-flex justify-content-start align-items-center">
<div

View file

@ -19,6 +19,7 @@ import {
faCircleCheck,
faCircleNotch,
faCircleXmark,
faCopy,
faCrop,
faEraser,
faEye,
@ -94,6 +95,7 @@ library.add(faEraser,
faFile,
faGear,
faBan,
faCopy,
);
// Front

View file

@ -2,7 +2,7 @@ import type { SweetAlertResult } from "sweetalert2";
import confirm from "./sweetalertConfirm";
window.addEventListener("DOMContentLoaded", () => {
let elements = document.getElementsByClassName("confirmDeleteJS");
let elements = document.getElementsByClassName("confirmDeleteTS");
for (const element of elements) {
element.addEventListener("submit", function (e) {
e.preventDefault();

View file

@ -8,11 +8,24 @@
<body>
@include('bo.layouts.header')
<main class="container py-3">
{{-- Show a message when an action is performed --}}
@include('modules.flash-messages')
{{-- Main content --}}
@yield('content')
<!-- Main content -->
<main class="container-fluid">
<div class="row justify-content-center p-3 pt-0">
<!-- Show a message when an action is performed -->
<div class="container">
<div class="row justify-content-center">
<div class="col-12 col-md-10 col-lg-8 px-2">
@include('modules.flash-messages')
</div>
</div>
</div>
<!-- Main content -->
<div class="row justify-content-center">
<div class="col-12 col-md-10 col-lg-8 p-md-0 pt-3">
@yield('content')
</div>
</div>
</div>
</main>
<!-- Footer -->

View file

@ -1,10 +1,8 @@
<footer class="bg-body-tertiary px-0 text-center" id="footer">
<div class="p-2 text-center">
@if (isset($cpsr_name) && isset($cpsr_version))
<span class="text-reset">{{ $cpsr_name }}&nbsp;-&nbsp;{{ $cpsr_version }}</span>
<span>-</span>
@endif
<span class="text-reset fw-bold">{{ __('UWIL') }}</span>
© {{ date('Y') }}
</div>
<footer id="footer" class="d-flex justify-content-center align-items-center bg-body-tertiary text-center w-100 px-0">
@if (isset($cpsr_name) && isset($cpsr_version))
<span class="text-reset">{{ $cpsr_name }}&nbsp;-&nbsp;{{ $cpsr_version }}</span>
<span>-</span>
@endif
<span class="text-reset fw-bold">{{ __('UWIL') }}</span>
© {{ date('Y') }}
</footer>

View file

@ -31,10 +31,11 @@
<meta name="msapplication-TileColor" content="#ffffff">
<meta name="theme-color" content="#ffffff">
@include('modules.breadcrumb.breadcrumb-head')
@include('modules.breadcrumbs.breadcrumb-head')
<!-- STYLES -->
@vite(['resources/sass/bo/bo.scss'])
@stack('styles')
@if(config('app.debug'))
<script nonce="{{ $nonce }}">window.vueDebug = true;</script>

View file

@ -1,6 +1,6 @@
<nav class="navbar navbar-expand-lg position-fixed fixed-top bg-body-tertiary shadow-sm" id="top-navbar">
<nav class="navbar navbar-expand-xxl position-fixed fixed-top bg-body-tertiary shadow-sm" id="top-navbar">
<div class="container">
<a class="navbar-brand text-center" data-bs-toggle="tooltip" data-bs-placement="bottom" href="{{ route('bo.default-tenant') }}"
<a class="navbar-brand text-center" data-bs-toggle="tooltip" data-bs-placement="bottom" href="{{ route('bo.dashboard') }}"
title="{{ __('Accéder au front du projet') }}">
{{ config('app.name') }}
</a>
@ -21,36 +21,38 @@
@auth
@php
$user = auth('backend')->user();
$tenantModels = $user->can('isAdmin') ? $tenantModels : $user->tenants;
$tenantsGlobal = $user->can('isAdmin') ? $tenantsGlobal : $user->tenants;
@endphp
<!-- Tenant switchs -->
<li class="nav-item dropdown @auth border-end @endauth">
<button class="btn nav-link dropdown-toggle d-flex align-items-center h-100 flex-row border-0 px-3"
data-bs-toggle="dropdown" type="button" aria-expanded="false">
{{ tenant('name') }}
</button>
<div class="dropdown-menu dropdown-menu-end">
@foreach ($tenantModels as $tenantModel)
<a class="dropdown-item @if (tenant()->getKey() === $tenantModel->getKey()) active @endif" href="{{ route('bo.dashboard', ['tenant' => $tenantModel]) }}">
{{ $tenantModel->name }}
</a>
@endforeach
</div>
</li>
@if (!request()->routeIs('bo.dashboard') && !request()->routeIs('bo.tenants.*') && !request()->routeIs('bo.users.*') && $tenantsGlobal->count() > 1)
<!-- Tenant switchs -->
<li class="nav-item dropdown @auth border-end @endauth">
<button class="btn nav-link dropdown-toggle d-flex align-items-center h-100 flex-row border-0 pe-3"
data-bs-toggle="dropdown" type="button" aria-expanded="false">
{{ tenant('name') }}
</button>
<div class="dropdown-menu dropdown-menu-end">
@foreach ($tenantsGlobal as $tenantModel)
<a class="dropdown-item @if (tenant()->getKey() === $tenantModel->getKey()) active @endif"
href="{{ route(request()->route()->getName(), array_merge(['tenant' => $tenantModel], request()->route()->parameters())) }}">
{{ $tenantModel->name }}
</a>
@endforeach
</div>
</li>
@endif
<!-- Profile -->
<li class="nav-item dropdown">
<button class="btn nav-link dropdown-toggle d-flex align-items-center h-100 flex-row border-0 ps-3" id="navbarDropdown"
<button class="btn nav-link dropdown-toggle d-flex align-items-center h-100 flex-row border-0 ps-xxl-3" id="navbarDropdown"
data-bs-toggle="dropdown" type="button">
<div class="d-flex flex-column align-items-end lh-1">
<span
class="fw-bold">{{ auth('backend')->user()->first_name }}&nbsp;{{ auth('backend')->user()->last_name }}</span>
<small class="text-secondary-body">{{ Str::of(auth('backend')->user()->role->label())->ucFirst() }}</small>
<div class="d-flex flex-column align-items-start align-items-xxl-end lh-1">
<span class="fw-bold">{{ $user->first_name }}&nbsp;{{ $user->last_name }}</span>
<small class="text-secondary-body">{{ Str::of($user->role->label())->ucFirst() }}</small>
</div>
</button>
<div class="dropdown-menu dropdown-menu-end m-0 text-center" aria-labelledby="navbarDropdown">
<a class="dropdown-item w-100 text-decoration-none p-2" data-bs-toggle="tooltip" data-bs-placement="left"
href="{{ route('bo.users.edit', ['tenant' => tenant(), 'user' => auth('backend')->user()->getKey()]) }}"
title="{{ __('Accéder à son profile') }}">
<a class="dropdown-item w-100 text-decoration-none p-2 @if(request()->routeIs('bo.users.edit') && request()->route('user')->getKey() === $user->getKey()) active @endif"
data-bs-toggle="tooltip" data-bs-placement="left" title="{{ __('Accéder à son profile') }}"
href="{{ route('bo.users.edit', ['user' => $user->getKey()]) }}">
{{ __('Profile') }}
</a>
<hr class="dropdown-divider m-0">

View file

@ -0,0 +1,78 @@
@auth('backend')
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link @if (request()->routeIs('bo.dashboard')) active @endif" data-bs-toggle="tooltip"
href="{{ route('bo.dashboard') }}"
title="{{ __('Revenir au :model', ['model' => __('back.home')]) }}">
{{ __('back.home') }}
</a>
</li>
@php $user = auth('backend')->user(); @endphp
@if (($tenantsGlobal->count() && $user->can('isAdmin')) || ($user->tenants->count() && !$user->can('isAdmin')))
<li class="nav-item">
<a class="nav-link @if (request()->routeIs('bo.app_users.*')) active @endif" data-bs-toggle="tooltip"
href="{{ route('bo.app_users.index', ['tenant' => tenant()]) }}" title="{{ __('Liste des :model', ['model' => str(__('models.classes.app_user'))->plural()]) }}">
{{ str(__('models.classes.app_user'))->plural() }}
</a>
</li>
<li class="nav-item">
<a class="nav-link @if (request()->routeIs('bo.directories.*')) active @endif" data-bs-toggle="tooltip"
href="{{ route('bo.directories.index', ['tenant' => tenant()]) }}" title="{{ __('Liste des :model', ['model' => str(__('models.classes.directory'))->plural()]) }}">
{{ str(__('models.classes.directory'))->plural() }}
</a>
</li>
<li class="nav-item">
<a class="nav-link @if (request()->routeIs('bo.documents.*')) active @endif" data-bs-toggle="tooltip"
href="{{ route('bo.documents.index', ['tenant' => tenant()]) }}" title="{{ __('Liste des :model', ['model' => str(__('models.classes.document'))->plural()]) }}">
{{ str(__('models.classes.document'))->plural() }}
</a>
</li>
<li class="nav-item">
<a class="nav-link @if (request()->routeIs('bo.tags.*')) active @endif" data-bs-toggle="tooltip" href="{{ route('bo.tags.index', ['tenant' => tenant()]) }}"
title="{{ __('Liste des :model', ['model' => str(__('models.classes.tag'))->plural()]) }}">
{{ str(__('models.classes.tag'))->plural() }}
</a>
</li>
<li class="nav-item">
<a class="nav-link @if (request()->routeIs('bo.formularies.*')) active @endif" data-bs-toggle="tooltip"
href="{{ route('bo.formularies.index', ['tenant' => tenant()]) }}"
title="{{ __('Liste des champs du :model', ['model' => __('models.classes.formulary')]) }}">
{{ __('models.classes.formulary') }}
</a>
</li>
<li class="nav-item">
<a class="nav-link @if (request()->routeIs('bo.contacts.*')) active @endif" data-bs-toggle="tooltip"
href="{{ route('bo.contacts.index', ['tenant' => tenant()]) }}" title="{{ __('Liste des :model', ['model' => str(__('models.classes.contact'))->plural()]) }}">
{{ str(__('models.classes.contact'))->plural() }}
</a>
</li>
<li class="nav-item">
<a class="nav-link @if (request()->routeIs('bo.settings.*')) active @endif" data-bs-toggle="tooltip"
href="{{ route('bo.settings.index', ['tenant' => tenant()]) }}" title="{{ __('Liste des :model', ['model' => str(__('models.classes.setting'))->plural()]) }}">
{{ str(__('models.classes.setting'))->plural() }}
</a>
</li>
@endif
@can('isAdmin')
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle @if (request()->routeIs('bo.users.*') || request()->routeIs('bo.tenants.*')) active @endif" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
{{__('back.admin') }}
</a>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item @if (request()->routeIs('bo.users.*')) active @endif" data-bs-toggle="tooltip"
href="{{ route('bo.users.index') }}" title="{{ __('Liste des :model', ['model' => str(__('models.classes.user'))->plural()]) }}">
{{ str(__('models.classes.user'))->plural() }}
</a>
</li>
<li>
<a class="dropdown-item @if (request()->routeIs('bo.tenants.*')) active @endif" data-bs-toggle="tooltip"
href="{{ route('bo.tenants.index') }}" title="{{ __('Liste des :model', ['model' => str(__('models.classes.tenant'))->plural()]) }}">
{{ str(__('models.classes.tenant'))->plural() }}
</a>
</li>
</ul>
</li>
@endcan
</ul>
@endauth

View file

@ -1,75 +0,0 @@
@auth('backend')
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link @if (request()->routeIs('bo.dashboard')) active @endif" data-bs-toggle="tooltip"
href="{{ route('bo.dashboard', ['tenant' => tenant()]) }}"
title="{{ __('Revenir au :model', ['model' => __('back.home')]) }}">
{{ __('back.home') }}
</a>
</li>
<li class="nav-item">
<a class="nav-link @if (request()->routeIs('bo.app-users.*')) active @endif" data-bs-toggle="tooltip"
href="{{ route('bo.app-users.index', ['tenant' => tenant()]) }}" title="{{ __('Liste des :model', ['model' => __('models.classes.users')]) }}">
{{ __('models.classes.app_users') }}
</a>
</li>
<li class="nav-item">
<a class="nav-link @if (request()->routeIs('bo.directories.*')) active @endif" data-bs-toggle="tooltip"
href="{{ route('bo.directories.index', ['tenant' => tenant()]) }}" title="{{ __('Liste des :model', ['model' => __('models.classes.directories')]) }}">
{{ __('models.classes.directories') }}
</a>
</li>
<li class="nav-item">
<a class="nav-link @if (request()->routeIs('bo.documents.*')) active @endif" data-bs-toggle="tooltip"
href="{{ route('bo.documents.index', ['tenant' => tenant()]) }}" title="{{ __('Liste des :model', ['model' => __('models.classes.documents')]) }}">
{{ __('models.classes.documents') }}
</a>
</li>
<li class="nav-item">
<a class="nav-link @if (request()->routeIs('bo.tags.*')) active @endif" data-bs-toggle="tooltip" href="{{ route('bo.tags.index', ['tenant' => tenant()]) }}"
title="{{ __('Liste des :model', ['model' => __('models.classes.tags')]) }}">
{{ __('models.classes.tags') }}
</a>
</li>
<li class="nav-item">
<a class="nav-link @if (request()->routeIs('bo.formularies.*')) active @endif" data-bs-toggle="tooltip"
href="{{ route('bo.formularies.index', ['tenant' => tenant()]) }}"
title="{{ __('Liste des champs du :model', ['model' => Str::singular(__('models.classes.formularies'))]) }}">
{{ Str::singular(__('models.classes.formularies')) }}
</a>
</li>
<li class="nav-item">
<a class="nav-link @if (request()->routeIs('bo.contacts.*')) active @endif" data-bs-toggle="tooltip"
href="{{ route('bo.contacts.index', ['tenant' => tenant()]) }}" title="{{ __('Liste des :model', ['model' => __('models.classes.contacts')]) }}">
{{ __('models.classes.contacts') }}
</a>
</li>
<li class="nav-item">
<a class="nav-link @if (request()->routeIs('bo.settings.*')) active @endif" data-bs-toggle="tooltip"
href="{{ route('bo.settings.index', ['tenant' => tenant()]) }}" title="{{ __('Liste des :model', ['model' => __('models.classes.settings')]) }}">
{{__('models.classes.settings') }}
</a>
</li>
@can('isAdmin')
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle @if (request()->routeIs('bo.users.*') || request()->routeIs('bo.tenants.*')) active @endif" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
{{__('back.admin') }}
</a>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item @if (request()->routeIs('bo.users.*')) active @endif" data-bs-toggle="tooltip"
href="{{ route('bo.users.index', ['tenant' => tenant()]) }}" title="{{ __('Liste des :model', ['model' => __('models.classes.users')]) }}">
{{__('models.classes.users') }}
</a>
</li>
<li>
<a class="dropdown-item @if (request()->routeIs('bo.tenants.*')) active @endif" data-bs-toggle="tooltip"
href="{{ route('bo.tenants.index') }}" title="{{ __('Liste des :model', ['model' => __('models.classes.tenants')]) }}">
{{__('models.classes.tenants') }}
</a>
</li>
</ul>
</li>
@endcan
</ul>
@endauth

View file

@ -1,24 +1,26 @@
<nav class="pagination-custom d-flex justify-content-between align-items-center">
{{-- SELECT ITEMS PER PAGE --}}
<div class="dropup-center dropup d-flex justify-content-center align-items-center input-group w-fit">
<span class="btn-md input-group-text" data-bs-toggle="tooltip" data-bs-placement="top"
title="{{ __('crud.pagination.paginate_list') }}">
<i class="fa-solid fa-list-ul"></i>
</span>
<button class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" type="button" aria-expanded="false">
{{ Cache::get('pagination.' .Str::slug(request()->route()->getName())) }}
</button>
<ul class="dropdown-menu">
@foreach (\Uwil\LaravelCommon\Enums\ItemsPerPaginationEnum::toArray() as $itemsPerPaginationEnum)
<li>
<a class="dropdown-item text-center"
href="{{ request()->fullUrlWithQuery(['pagination' => $itemsPerPaginationEnum->value]) }}">
{{ $itemsPerPaginationEnum->value }}
</a>
</li>
@endforeach
</ul>
</div>
@if ($paginator->total() > \Uwil\LaravelCommon\Enums\ItemsPerPaginationEnum::five->value())
<div class="dropup-center dropup d-flex justify-content-center align-items-center input-group w-fit">
<span class="btn-md input-group-text" data-bs-toggle="tooltip" data-bs-placement="top"
title="{{ __('crud.pagination.paginate_list') }}">
<i class="fa-solid fa-list-ul"></i>
</span>
<button class="btn btn-primary dropdown-toggle" data-bs-toggle="dropdown" type="button" aria-expanded="false">
{{ Cache::get('pagination.' .Str::slug(request()->route()->getName())) }}
</button>
<ul class="dropdown-menu">
@foreach (\Uwil\LaravelCommon\Enums\ItemsPerPaginationEnum::toArray() as $itemsPerPaginationEnum)
<li>
<a class="dropdown-item text-center"
href="{{ request()->fullUrlWithQuery(['pagination' => $itemsPerPaginationEnum->value]) }}">
{{ $itemsPerPaginationEnum->value }}
</a>
</li>
@endforeach
</ul>
</div>
@endif
@if ($paginator->hasPages())
{{-- PAGINATION --}}
<div class="d-flex justify-content-end align-items-center">

View file

@ -1,8 +1,8 @@
<div class="row">
<div class="col-12">
<div class="col-12 my-3">
<form id="searchForm" action="{{ request()->url() }}">
<div class="input-group">
<label class="input-group-text" for="searchField">{{ $searchFields }}</label>
<label class="input-group-text" for="searchField">{{ str($searchFields)->ucFirst() }}</label>
{{-- CONTACTS FILTER --}}
@if (request()->user)
<input class="d-none" name="user" type="text" value="{{ request()->user }}">

View file

@ -1,53 +0,0 @@
@extends('bo.layout')
@section('title', sprintf('%s - %s', Str::singular(__('models.classes.app_users')), Str::ucfirst(__('création'))))
@section('description', __('Page de création d\'un nouvel utilisateur de l\'application.'))
@section('breadcrumb', request()->route()->getName())
@section('content')
<div class="card">
<div class="card-header">
<div class="row">
<div class="col">
<h1 class="text-center text-md-start h2">
<a href="{{ route('bo.app-users.index', ['tenant' => \tenant()]) }}"
class="btn btn-primary text-decoration-none m-0"
data-bs="tooltip"
data-bs-placement="top"
title="{{ __('Return to the :model list', ['model' => __('models.classes.app_users')]) }}"
data-bs-toggle="tooltip">
<i class="fa-solid fa-arrow-left"></i>
</a>
<span class="fw-bold">{{ Str::singular(__('models.classes.app_users')) }}</span>&nbsp;<small class="text-muted">{{ Str::ucfirst(__('création')) }}</small>
</h1>
</div>
<div class="col-12 col-md-4 d-flex justify-content-center justify-content-md-end align-items-center">
<button id="formSubmitClone"
class="btn btn-primary pull-end"
title="{{ __('Sauvegarder l\':model', ['model' => Str::singular(__('models.classes.app_users'))]) }}"
data-bs-toggle="tooltip">
<i class="fa-solid fa-floppy-disk"></i>
</button>
</div>
</div>
</div>
<div class="card-body bg-light">
<form action="{{ route('bo.app-users.store', ['tenant' => \tenant()]) }}" method="POST">
@csrf
@include('bo.pages.app-users.form')
<div class="row">
<div class="col text-center">
<button id="formSubmit"
type="submit"
class="btn btn-primary"
title="{{ __('Sauvegarder l\':model', ['model' => Str::singular(__('models.classes.app_users'))]) }}"
data-bs-toggle="tooltip">
{{ __('Sauvegarder') }}
</button>
</div>
</div>
</form>
</div>
</div>
@endsection

View file

@ -1,66 +0,0 @@
@extends('bo.layout', ['brParam' => $app_userModel])
@section('title', sprintf('%s - %s %s', Str::singular(__('models.classes.app_users')), Str::ucfirst(__('édition')), $app_userModel->lastname))
@section('description', __('Page d\'édition d\'un utilisateurs de l\'application.'))
@section('breadcrumb', request()->route()->getName())
@section('content')
<div class="card">
<div class="card-header">
<div class="row">
<div class="col">
<h1 class="text-center text-md-start h2">
<a href="{{ route('bo.app-users.index', ['tenant' => \tenant()]) }}"
class="btn btn-primary text-decoration-none m-0"
data-bs="tooltip"
data-bs-placement="top"
title="{{ __('Retourner sur la liste des :model', ['model' => __('models.classes.app_users')]) }}"
data-bs-toggle="tooltip">
<i class="fa-solid fa-arrow-left"></i>
</a>
<span class="fw-bold">{{ Str::singular(__('models.classes.app_users')) }}</span>&nbsp;<small class="text-muted">{{ Str::ucfirst(__('édition')) }}</small>
</h1>
</div>
<div class="col-12 col-md-4 d-flex justify-content-center justify-content-md-end align-items-center">
<form action="{{ route('bo.app-users.destroy', ['tenant' => \tenant(), 'app_user' => $app_userModel]) }}"
method="POST" class="d-inline confirmDeleteJS">
@method('DELETE')
@csrf
<div class="btn-group" role="group">
<button id="formSubmitClone"
class="btn btn-primary pull-end"
title="{{ __('Sauvegarder l\':model', ['model' => Str::singular(__('models.classes.app_users'))]) }}"
data-bs-toggle="tooltip">
<i class="fa-solid fa-floppy-disk"></i>
</button>
<button class="btn btn-danger"
title="{{ __('Supprimer l\':model', ['model' => Str::singular(__('models.classes.app_users'))]) }}"
data-bs-toggle="tooltip">
<i class="fa-solid fa-trash"></i>
</button>
</div>
</form>
</div>
</div>
</div>
<div class="card-body bg-light">
<form action="{{ route('bo.app-users.update', ['tenant' => \tenant(), 'app_user' => $app_userModel]) }}" method="POST">
@csrf
@method('PUT')
@include('bo.pages.app-users.form', ['model' => $app_userModel])
<div class="row">
<div class="col text-center">
<button id="formSubmit"
type="submit"
class="btn btn-primary"
title="{{ __('Sauvegarder l\':model', ['model' => Str::singular(__('models.classes.app_users'))]) }}"
data-bs-toggle="tooltip">
{{ __('Sauvegarder') }}
</button>
</div>
</div>
</form>
</div>
</div>
@endsection

View file

@ -1,80 +0,0 @@
<div class="table-responsive">
<table class="table fix-action">
<thead>
@php
$cols = [
'lastname' => __('Nom'),
'firstname' => __('Prénom'),
'email' => __('Email'),
'order' => __('Order')
];
@endphp
@include('bo.modules.table-col-sorter', [
'cols' => $cols,
'mobileHide' => [],
])
</thead>
<tbody>
@if ($app_users->count())
@foreach ($app_users as $app_usersModel)
<tr>
<td>
<span>{{ $app_usersModel->lastname }}</span>
</td>
<td class="text-center">
<span>{{ $app_usersModel->firstname }}</span>
</td>
<td class="text-center">
<span>{{ $app_usersModel->email }}</span>
</td>
@php $routeName = request()->route()->getName(); @endphp
@if (empty(request()->search) && Session::get("$routeName.sort_col") === 'order')
@include('bo.modules.change-model-order', [
'routeName' => 'appUsers',
'models' => $app_users,
'model' => $app_usersModel,
])
@endif
<td>
<div class="btn-group">
<form action="{{ route('bo.app-users.destroy', [
'tenant' => \tenant(),
'app_user' => $app_usersModel
]) }}" method="POST" class="d-inline confirmDeleteJS">
@method('DELETE')
@csrf
<div class="btn-group" role="group">
<a id="formSubmitClone"
href="{{ route('bo.app-users.edit', [
'tenant' => \tenant(),
'app_user' => $app_usersModel
]) }}"
class="btn btn-sm btn-primary"
role="button"
title="{{ __('Modifier l\':model', ['model' => Str::singular(__('models.classes.users'))]) }}"
data-bs-toggle="tooltip">
<i class="fa-solid fa-pencil"></i>
</a>
<button class="btn btn-sm btn-danger"
title="{{ __('Supprimer l\':model', ['model' => Str::singular(__('models.classes.users'))]) }}"
data-bs-toggle="tooltip">
<i class="fa-solid fa-trash"></i>
</button>
</div>
</form>
</div>
</td>
</tr>
@endforeach
@else
<tr>
<td colspan="{{ count($cols) + 1 }}" class="text-center">
<span class="h3">
{{ __('Aucun résultat pour les :models', ['models' => __('models.classes.users')]) }}
</span>
</td>
</tr>
@endif
</tbody>
</table>
</div>

View file

@ -1,53 +0,0 @@
@extends('bo.layout')
@section('title', sprintf('%s - %s', __('models.classes.app_users'), Str::ucfirst(__('liste'))))
@section('description', __('Page de contôle sur tous les utilisateurs de l\'application.'))
@section('breadcrumb', request()->route()->getName())
@section('content')
<div class="card">
<div class="card-header">
<div class="row">
<div class="col">
<h1 class="col h2">
<span class="fw-bold">{{ __('models.classes.app_users') }}</span>&nbsp;<small class="text-muted">{{ Str::ucfirst(__('liste')) }}</small>
</h1>
</div>
<div class="col d-flex align-items-center">
<form class="input-group"
action="{{ route('bo.app-users.import', ['tenant' => tenant()]) }}"
method="POST"
enctype="multipart/form-data">
<input class="form-control" type="file" name="import" accept="text/csv,.csv"
title="{{ __('Sélectionner un fichier .csv') }}"
data-bs-toggle="tooltip" required>
@csrf
<button type="submit" class="btn btn-primary"
title="{{ __('Importer le fichier .csv') }}"
data-bs-toggle="tooltip">
{{ __('Importer') }}
</button>
</form>
<div class="ms-1 justify-content-end">
<a href="{{ route('bo.app-users.create', ['tenant' => \tenant()]) }}"
class="btn btn-primary"
title="{{ __('Créer un(e) :model', ['model' => Str::singular(__('models.classes.app_users'))]) }}"
data-bs-toggle="tooltip">
<i class="fa-solid fa-plus"></i>
</a>
</div>
</div>
</div>
</div>
<div class="card-body">
@include('bo.modules.search-bar')
<div class="row mt-3">
<div class="col">
@include('bo.pages.app-users.index-list')
</div>
</div>
{{ $app_users->withQueryString()->onEachSide(1)->links() }}
</div>
</div>
@endsection

View file

@ -11,7 +11,11 @@
@endif
<ul>
@foreach ($appUser->directories as $directory)
<li><a href="{{ route('bo.directories.edit', $directory) }}">{{ $directory->name['fr'] }}</a></li>
<li>
<a href="{{ route('bo.directories.edit', ['tenant' => \tenant(), 'directory' => $directory]) }}">
{{ $directory->name['fr'] }}
</a>
</li>
@endforeach
</ul>
</div>

View file

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

View file

@ -0,0 +1,68 @@
@extends('bo.layout', ['brParam' => $appUserModel])
@section('title', __('Édition d\'un :model', ['model' => __('models.classes.app_user')]))
@section('description', __('Édition d\'un :model déjà existant', ['model' => __('models.classes.app_user')]))
@section('breadcrumb', request()->route()->getName())
@section('content')
<div class="d-flex justify-content-between flex-md-nowrap align-items-center flex-wrap pb-3">
<div class="d-flex align-items-start flex-row">
@can('viewAny', $appUserModel)
<a class="btn btn-primary text-decoration-none m-0" data-bs-toggle="tooltip"
href="{{ route('bo.app_users.index', ['tenant' => \tenant()]) }}"
title="{{ __('crud.helpers.list_all', ['model' => str(__('models.classes.app_user'))->plural()]) }}">
<i class="fa-solid fa-arrow-left"></i>
</a>
@endif
@include('modules.breadcrumbs.breadcrumb-body', ['brParam' => $appUserModel])
</div>
<div class="mb-md-0 mb-2">
@can('delete', $appUserModel)
<form class="confirmDeleteTS" action="{{ route('bo.app_users.destroy', ['tenant' => \tenant(), 'app_user' => $appUserModel]) }}"
method="POST" novalidate>
@endcan
<div class="btn-group">
@can('duplicate', $appUserModel)
<a class="btn btn-secondary" data-bs-toggle="tooltip"
href="{{ route('bo.app_users.duplicate', ['tenant' => \tenant(), 'app_user' => $appUserModel]) }}"
title="{{ str(__('crud.actions.duplicate'))->ucFirst() }}">
<i class="fa-solid fa-copy"></i>
</a>
@endcan
@can('update', $appUserModel)
<button class="btn btn-primary" id="formSubmitClone" data-bs-toggle="tooltip" type="submit"
title="{{ __('crud.helpers.save_model', ['model' => __('models.classes.app_user')]) }}">
{{ str(__('crud.actions.save'))->ucFirst() }}
</button>
@endcan
@can('delete', $appUserModel)
@csrf
@method('DELETE')
<button class="btn btn-danger" data-bs-toggle="tooltip" type="submit" title="{{ str(__('crud.actions.delete'))->ucFirst() }}">
<i class="fa-solid fa-trash"></i>
</button>
@endcan
</div>
@can('delete', $appUserModel)
</form>
@endcan
</div>
</div>
@can('update', $appUserModel)
<form action="{{ route('bo.app_users.update', ['tenant' => \tenant(), 'app_user' => $appUserModel]) }}" method="POST" enctype="multipart/form-data">
@csrf
@method('PUT')
@endcan
@include('bo.pages.app_users.form')
@can('update', $appUserModel)
<div class="row">
<div class="col text-center">
<button class="btn btn-primary" id="formSubmit" data-bs-toggle="tooltip" type="submit"
title="{{ __('crud.helpers.save_model', ['model' => __('models.classes.app_user')]) }}">
{{ str(__('crud.actions.save'))->ucFirst() }}
</button>
</div>
</div>
</form>
@endcan
@endsection

View file

@ -7,11 +7,11 @@
<div class="col-12 mb-3">
@include('bo.partials.inputs.varchar', [
'id' => 'app_userSurname',
'label' => __('Nom de l\':model', ['model' => Str::singular(__('models.classes.users'))]),
'label' => __('Nom de l\':model', ['model' => __('models.classes.user')]),
'hasWordCounter' => true,
'name' => 'lastname',
'required' => true,
'model' => $app_userModel ?? null,
'model' => $appUserModel ?? null,
'helper' => __('Doit posséder 3 caractères minimum, 255 maximum.'),
'placeholder' => 'Un beau nom',
])
@ -27,11 +27,11 @@
<div class="col-12 mb-3">
@include('bo.partials.inputs.varchar', [
'id' => 'app_userFirstname',
'label' => __('Prénom de l\':model', ['model' => Str::singular(__('models.classes.users'))]),
'label' => __('Prénom de l\':model', ['model' => __('models.classes.user')]),
'hasWordCounter' => true,
'name' => 'firstname',
'required' => true,
'model' => $app_userModel ?? null,
'model' => $appUserModel ?? null,
'helper' => __('Doit posséder 3 caractères minimum, 255 maximum.'),
'placeholder' => 'Un beau prénom',
])
@ -47,11 +47,11 @@
<div class="col-12 mb-3">
@include('bo.partials.inputs.varchar-email', [
'id' => 'app_userEmail',
'label' => __('Email de l\':model', ['model' => Str::singular(__('models.classes.users'))]),
'label' => __('Email de l\':model', ['model' => __('models.classes.user')]),
'name' => 'email',
'hasWordCounter' => true,
'required' => true,
'model' => $app_userModel ?? null,
'model' => $appUserModel ?? null,
'helper' => __(
'Tapez votre e-mail en respectant la convention d\'appellation des e-mails (ex: firstname.lastname@yourdomain.com).'),
'placeholder' => 'firstname.lastname@yourdomain.com',
@ -68,7 +68,7 @@
<div class="col-12 col-md-6 mb-3">
@include('bo.partials.inputs.varchar-password', [
'id' => 'app_userPassword',
'label' => __('Mot de passe de l\':model', ['model' => Str::singular(__('models.classes.users'))]),
'label' => __('Mot de passe de l\':model', ['model' => __('models.classes.user')]),
'name' => 'password',
'hasWordCounter' => false,
'required' => false,
@ -80,7 +80,7 @@
@include('bo.partials.inputs.varchar-password', [
'id' => 'app_userPassword_confirmation',
'label' => __('Confirmation du mot de passe de l\':model', [
'model' => Str::singular(__('models.classes.users')),
'model' => __('models.classes.user'),
]),
'name' => 'password_confirmation',
'hasWordCounter' => false,
@ -102,7 +102,7 @@
'id' => 'app_userAuthorizedFolders',
'name' => 'directories',
'label' => __('Dossiers autorisés'),
'model' => $app_userModel ?? null,
'model' => $appUserModel ?? null,
'itemsList' => $directories,
'placeholder' => __('Recherchez un dossier'),
'helper' => __('Dossiers autorisés de l\'utilisateur.'),
@ -119,11 +119,11 @@
<div class="col-12 mb-3">
@include('bo.partials.inputs.tinyint-enum', [
'id' => 'app_userRole',
'label' => __('Rôle de l\':model', ['model' => Str::singular(__('models.classes.users'))]),
'label' => __('Rôle de l\':model', ['model' => __('models.classes.user')]),
'name' => 'role',
'model' => $app_userModel ?? null,
'model' => $appUserModel ?? null,
'required' => true,
'associatedDatas' => \App\Enums\AppUserRolesEnum::toArray(),
'itemsList' => \App\Enums\AppUserRolesEnum::toArray(),
'helper' => __('Rôle de l\'utilisateur.'),
])
</div>
@ -140,7 +140,7 @@
'id' => 'app_userStatus',
'name' => 'status',
'label' => __('L\'utilisateur est-il activé ?'),
'model' => $app_userModel ?? null,
'model' => $appUserModel ?? null,
'helper' => __('Activation de l\'utilisateur de l\'application.'),
])
</div>

View file

@ -0,0 +1,92 @@
<div class="table-responsive">
<table class="table fix-action">
<thead>
@php
$cols = [
'lastname' => str(__('validation.attributes.last_name'))->ucFirst(),
'firstname' => str(__('validation.attributes.first_name'))->ucFirst(),
'email' => str(__('validation.attributes.email'))->ucFirst(),
'order' => str(__('validation.custom.order'))->ucFirst(),
];
@endphp
@include('bo.modules.table-col-sorter', [
'cols' => $cols,
'mobileHide' => [],
])
</thead>
<tbody>
@if ($appUserModels->count())
@foreach ($appUserModels as $appUserModel)
<tr>
<td class="text-center">
{{ $appUserModel->lastname }}
</td>
<td class="text-center">
{{ $appUserModel->firstname }}
</td>
<td class="text-center">
{{ $appUserModel->email }}
</td>
@php $routeName = request()->route()->getName(); @endphp
@if (empty(request()->search) && Session::get("$routeName.sort_col") === 'order')
@include('bo.modules.change-model-order', [
'routeName' => 'app_users',
'models' => $appUserModels,
'model' => $appUserModel,
])
@endif
<td>
@can('delete', $appUserModel)
<form action="{{ route('bo.app_users.destroy', [
'tenant' => \tenant(),
'app_user' => $appUserModel,
]) }}" method="POST" class="d-inline confirmDeleteTS">
@endcan
<div class="btn-group" role="group">
@can('duplicate', $appUserModel)
<a class="btn btn-sm btn-secondary" data-bs-toggle="tooltip"
href="{{ route('bo.app_users.duplicate', ['tenant' => \tenant(), 'app_user' => $appUserModel]) }}"
title="{{ __('Dupliquer l\':model', ['model' => __('models.classes.app_user')]) }}">
<i class="fa-solid fa-copy"></i>
</a>
@endcan
@can('update', $appUserModel)
<a id="formSubmitClone"
href="{{ route('bo.app_users.edit', [
'tenant' => \tenant(),
'app_user' => $appUserModel,
]) }}"
class="btn btn-sm btn-primary" role="button"
title="{{ __('Modifier l\':model', ['model' => __('models.classes.app_user')]) }}"
data-bs-toggle="tooltip">
<i class="fa-solid fa-pencil"></i>
</a>
@endcan
@can('delete', $appUserModel)
@csrf
@method('DELETE')
<button class="btn btn-sm btn-danger"
title="{{ __('Supprimer l\':model', ['model' => __('models.classes.app_user')]) }}"
data-bs-toggle="tooltip">
<i class="fa-solid fa-trash"></i>
</button>
@endcan
</div>
@can('delete', $appUserModel)
</form>
@endcan
</td>
</tr>
@endforeach
@else
<tr>
<td colspan="{{ count($cols) + 1 }}" class="text-center">
<span class="h3">
{{ __('Aucun résultat pour les :models', ['models' => str(__('models.classes.app_user'))->plural()]) }}
</span>
</td>
</tr>
@endif
</tbody>
</table>
</div>

View file

@ -0,0 +1,33 @@
@extends('bo.layout')
@section('title', sprintf('%s - %s', str(__('models.classes.app_user'))->plural(), str(__('liste'))->ucFirst()))
@section('description', __('Page de contôle de tous les :model de l\'application.', ['model' => str(__('models.classes.app_user'))->plural()]))
@section('breadcrumb', request()->route()->getName())
@section('content')
<div class="d-flex justify-content-between flex-md-nowrap align-items-center flex-wrap">
@include('modules.breadcrumbs.breadcrumb-body')
<form class="input-group me-1" action="{{ route('bo.app_users.import', ['tenant' => tenant()]) }}" method="POST"
enctype="multipart/form-data">
<input class="form-control" type="file" name="import" accept="text/csv,.csv"
title="{{ __('Sélectionner un fichier .csv') }}" data-bs-toggle="tooltip" required>
@csrf
<button type="submit" class="btn btn-primary" title="{{ __('Importer le fichier .csv') }}"
data-bs-toggle="tooltip">
{{ __('Importer') }}
</button>
</form>
@can('create', \App\Models\User::class)
<a class="btn btn-primary float-right" data-bs-toggle="tooltip"
href="{{ route('bo.app_users.create', ['tenant' => \tenant()]) }}"
title="{{ __('crud.helpers.create_model', ['model' => __('models.classes.app_user')]) }}">
<i class="fa-solid fa-plus"></i>
</a>
@endcan
</div>
@include('bo.modules.search-bar')
<div class="table-responsive">
@include('bo.pages.app_users.index-list')
</div>
{{ $appUserModels->links() }}
@endsection

View file

@ -3,11 +3,11 @@
<thead>
@php
$cols = [
'lastname' => __('Nom'),
'email' => __('Email'),
'phone' => __('Téléphone'),
'app_user_id' => __('Commercial'),
'updated_at' => __('Mis à jour'),
'lastname' => str(__('validation.attributes.last_name'))->ucFirst(),
'email' => str(__('validation.attributes.email'))->ucFirst(),
'phone' => str(__('validation.attributes.phone'))->ucFirst(),
'app_user_id' => str(__('models.classes.app_user'))->ucFirst(),
'updated_at' => str(__('validation.attributes.updated_at'))->ucFirst(),
];
@endphp
@include('bo.modules.table-col-sorter', [
@ -16,29 +16,37 @@
])
</thead>
<tbody>
@if ($contactModelList->count())
@foreach ($contactModelList as $contactModel)
@if ($contactModels->count())
@foreach ($contactModels as $contactModel)
<tr>
<td class="align-middle">
<span>{{ $contactModel->lastname }} {{ $contactModel->firsname }}</span>
<td class="text-center align-middle">
@if ($contactModel->firstname || $contactModel->lastname)
{{ $contactModel->lastname }} {{ $contactModel->firstname }}
@else
<small class="text-secondary fst-italic">{{ __('Non renseigné') }}</small>
@endif
</td>
<td class="align-middle">
<td class="text-center align-middle">
<a href="mailto:{{ $contactModel->email }}">{{ $contactModel->email }}</a>
</td>
<td class="align-middle">
<a href="tel:{{ $contactModel->phone }}">{{ $contactModel->phone }}</a>
<td class="text-center align-middle">
@if ($contactModel->phone)
<a href="tel:{{ $contactModel->phone }}">{{ $contactModel->phone }}</a>
@else
<small class="text-secondary fst-italic">{{ __('Non renseigné') }}</small>
@endif
</td>
<td class="align-middle">
<span>{{ $contactModel->appUser->lastname }} {{ $contactModel->appUser->firstname }}</span>
<td class="text-center align-middle">
{{ $contactModel->appUser->lastname }} {{ $contactModel->appUser->firstname }}
</td>
<td class="align-middle">
<span>{{ $contactModel->updated_at->isoFormat('\L\e DD/MM/YYYY \à HH:mm') }}</span>
<td class="text-center align-middle">
<span class="badge bg-secondary">{{ $contactModel->updated_at->isoFormat('LLLL') }}</span>
</td>
<td>
<div class="btn-group">
<a class="btn btn-sm btn-warning" data-bs-toggle="tooltip"
href="{{ route('bo.contacts.show', ['tenant' => \tenant(), 'contact' => $contactModel]) }}"
title="{{ __('Visualiser le :model', ['model' => Str::singular(__('models.classes.contacts'))]) }}"
title="{{ __('Visualiser le :model', ['model' => __('models.classes.contact')]) }}"
role="button">
<i class="fa-solid fa-eye"></i>
</a>
@ -50,7 +58,7 @@
<tr>
<td class="text-center" colspan="{{ count($cols) + 1 }}">
<span class="h3">
{{ __('Aucun résultat pour les :models', ['models' => __('models.classes.documents')]) }}
{{ __('Aucun résultat pour les :models', ['models' => str(__('models.classes.contact'))->plural()]) }}
</span>
</td>
</tr>

View file

@ -1,61 +1,45 @@
@extends('bo.layout')
@section('title', sprintf('%s - %s', __('models.classes.contacts'), Str::ucfirst(__('liste'))))
@section('description', __('Page de contôle sur tous les contacts de l\'application.'))
@section('title', sprintf('%s - %s', str(__('models.classes.contact'))->plural(), str(__('liste'))->ucFirst()))
@section('description', __('Page de contôle de tous les :model de l\'application.', ['model' => str(__('models.classes.contact'))->plural()]))
@section('breadcrumb', request()->route()->getName())
@section('content')
<div class="card">
<div class="card-header">
<div class="row">
<div class="col">
<h1 class="col h2">
<span class="fw-bold">{{ __('models.classes.contacts') }}</span>&nbsp;<small
class="text-muted">{{ Str::ucfirst(__('liste')) }}</small>
</h1>
</div>
<div class="col d-flex justify-content-end align-items-center">
<div class="justify-content-end ms-1">
<div class="input-group mb-3">
<label class="input-group-text" for="filter-app-user-field">{{ __('Filtrer') }}</label>
<select class="form-select" id="filter-app-user-field">
<option @if (!request()->user) selected @endif>{{ __('Choisir un commercial') }}</option>
@foreach ($appUsers as $appUser)
<option data-id="{{ $appUser->id }}" @if (request()->user == $appUser->id) selected @endif>
{{ __($appUser->lastname) }}&nbsp;{{ __($appUser->firstname) }}
</option>
@endforeach
</select>
<button class="btn btn-outline-secondary" id="reset-app-user-filter" type="button">
<i class="fa fa-delete-left"></i>
</button>
</div>
@push('scripts')
<script nonce="{{ $nonce ?? null }}">
document.addEventListener('DOMContentLoaded', function() {
const appUserFilter = document.getElementById('filter-app-user-field');
const appUserFilterReset = document.getElementById('reset-app-user-filter');
appUserFilterReset.addEventListener('click', () => window.location = '{{ route('bo.contacts.index', ['tenant' => \tenant()]) }}')
appUserFilter.addEventListener('change', (e) =>
window.location = '{{ route('bo.contacts.index', ['tenant' => \tenant(), 'user' => 'USER']) }}'.replace('USER', appUserFilter.options[
appUserFilter.options.selectedIndex].dataset.id ?? '')
)
});
</script>
@endpush
</div>
</div>
<div class="d-flex justify-content-between flex-md-nowrap align-items-center flex-wrap">
@include('modules.breadcrumbs.breadcrumb-body')
<div class="col-12 col-md-6 col-xl-5 col-xxl-4">
<div class="input-group">
<label class="input-group-text" for="filter-app-user-field">{{ __('Filtrer') }}</label>
<select class="form-select" id="filter-app-user-field">
<option @if (!request()->user) selected @endif>{{ __('Choisir un commercial') }}</option>
@foreach ($appUsers as $appUser)
<option data-id="{{ $appUser->id }}" @if (request()->user == $appUser->id) selected @endif>
{{ __($appUser->lastname) }}&nbsp;{{ __($appUser->firstname) }}
</option>
@endforeach
</select>
<button class="btn btn-outline-secondary" id="reset-app-user-filter" type="button">
<i class="fa fa-delete-left"></i>
</button>
</div>
</div>
<div class="card-body">
@include('bo.modules.search-bar')
<div class="row mt-3">
<div class="col">
@include('bo.pages.contacts.index-list')
</div>
</div>
{{ $contactModelList->withQueryString()->onEachSide(1)->links() }}
@push('scripts')
<script nonce="{{ $nonce ?? null }}">
document.addEventListener('DOMContentLoaded', function() {
const appUserFilter = document.getElementById('filter-app-user-field');
const appUserFilterReset = document.getElementById('reset-app-user-filter');
appUserFilterReset.addEventListener('click', () => window.location = '{{ route('bo.contacts.index', ['tenant' => \tenant()]) }}')
appUserFilter.addEventListener('change', (e) =>
window.location = '{{ route('bo.contacts.index', ['tenant' => \tenant(), 'user' => 'USER']) }}'.replace('USER', appUserFilter.options[
appUserFilter.options.selectedIndex].dataset.id ?? '')
)
});
</script>
@endpush
</div>
</div>
@include('bo.modules.search-bar')
<div class="table-responsive">
@include('bo.pages.contacts.index-list')
</div>
{{ $contactModels->links() }}
@endsection

View file

@ -1,6 +1,6 @@
@extends('bo.layout')
@section('title', sprintf('%s - %s', __('models.classes.contacts'), Str::ucfirst(__('liste'))))
@section('title', sprintf('%s - %s', str(__('models.classes.contact'))->plural(), Str::ucfirst(__('liste'))))
@section('description', __('Page de contôle sur tous les contacts de l\'application.'))
@section('breadcrumb', request()->route()->getName())
@ -13,10 +13,10 @@
<h1 class="text-md-start h2 text-center">
<a class="btn btn-primary text-decoration-none m-0" data-bs="tooltip" data-bs-placement="top" data-bs-toggle="tooltip"
href="{{ route('bo.contacts.index', ['tenant' => \tenant()]) }}"
title="{{ __('Retourner sur la liste des :model', ['model' => __('models.classes.contacts')]) }}">
title="{{ __('Retourner sur la liste des :model', ['model' => str(__('models.classes.contact'))->plural()]) }}">
<i class="fa-solid fa-arrow-left"></i>
</a>
<span class="fw-bold">{{ Str::singular(__('models.classes.contacts')) }}</span>&nbsp;<small
<span class="fw-bold">{{ __('models.classes.contact') }}</span>&nbsp;<small
class="text-muted">{{ Str::ucfirst(__('voir')) }}</small>
</h1>
</div>
@ -50,7 +50,7 @@
</div>
<div class="accordion-collapse collapse" id="collapseAppUser" data-bs-parent="#appUserAccordion">
<div class="accordion-body">
@include('bo.pages.app-users.card', ['appUser' => $contactModel->appUser])
@include('bo.pages.app_users.card', ['appUser' => $contactModel->appUser])
</div>
</div>
</div>

View file

@ -7,27 +7,54 @@
@section('content')
<div class="container">
<div class="row">
<div class="col">
@php $user = auth('backend')->user(); @endphp
@if (!$tenantsGlobal->count() && $user->can('isAdmin'))
<div class="col-12 mb-3">
<div class="card text-center border-danger">
<div class="card-body">
<h5 class="card-title text-center text-danger">
<b>
{{ __('Vous devez administrer au moins un tenant pour le bon fonctionnement de l\'appli/back-office.', [
'role' => $user->role->label(),
]) }}
</b>
</h5>
</div>
<div class="card-footer">
<a href="{{ route('bo.tenants.create') }}"
class="btn btn-danger">
{{ __('Créer le premier tenant') }}
</a>
</div>
</div>
</div>
@endif
<div class="col-12">
<div class="card text-center">
<div class="card-header fw-bold">
<h1 class="h2 fw-bold">{{ __('Dashboard') }}</h1>
</div>
<div class="card-body">
<h5 class="card-title">
{{ __('Bonjour :firstname :lastname !', [
'firstname' => Auth::user()->firstname,
'lastname' => Auth::user()->lastname,
<h5 class="card-title text-center">
<b>
{{ __('Bonjour :first_name :last_name !', [
'first_name' => $user->first_name,
'last_name' => $user->last_name,
]) }}
</b>
</h5>
<p class="card-text m-0">
<p class="card-text">
{{ __('Bienvenue dans le back-office, où vous pouvez gérer votre application web.') }}
</p>
<p class="card-text">
<p class="card-text mt-4">
<b>
{{ __('Pour accéder à vos statistiques') }} :
<a data-bs-toggle="tooltip" href="https://report.uwil.fr" title="{{ __('Accéder à notre outil de reporting') }}"
target="_blank">https://report.uwil.fr</a><br>
{{ __('Pour tout problème ou évolution souhaitée, veuillez nous contacter via notre outil : ') }}
<a data-bs-toggle="tooltip" href="https://support.uwil.fr" title="{{ __('Accéder à notre outil de ticketing') }}"
target="blank">
https://support.uwil.fr
</a>.
<a data-bs-toggle="tooltip" href="https://support.uwil.fr/" title="{{ __('Accéder à notre outil de ticketing') }}"
target="blank">https://support.uwil.fr/</a>.
</b>
</p>
</div>
</div>

View file

@ -1,65 +1,68 @@
@extends('bo.layout', ['brParam' => $directoryModel])
@section('title', sprintf('%s - %s %s', Str::singular(__('models.classes.directories')), Str::ucfirst(__('édition')), $directoryModel->lastname))
@section('description', __('Page d\'édition d\'un dossier de l\'application.'))
@section('title', __('Édition d\'un :model', ['model' => __('models.classes.directory')]))
@section('description', __('Édition d\'un :model déjà existant', ['model' => __('models.classes.directory')]))
@section('breadcrumb', request()->route()->getName())
@section('content')
<div class="card">
<div class="card-header">
<div class="row">
<div class="col">
<h1 class="text-center text-md-start h2">
<a href="{{ route('bo.directories.index', ['tenant' => \tenant()]) }}"
class="btn btn-primary text-decoration-none m-0"
data-bs="tooltip"
data-bs-placement="top"
title="{{ __('Retourner sur la liste des :model', ['model' => __('models.classes.directories')]) }}"
data-bs-toggle="tooltip">
<i class="fa-solid fa-arrow-left"></i>
</a>
<span class="fw-bold">{{ Str::singular(__('models.classes.directories')) }}</span>&nbsp;<small class="text-muted">{{ Str::ucfirst(__('édition')) }}</small>
</h1>
</div>
<div class="col-12 col-md-4 d-flex justify-content-center justify-content-md-end align-items-center">
<form action="{{ route('bo.directories.destroy', ['tenant' => \tenant(), 'directory' => $directoryModel]) }}" method="POST" class="d-inline confirmDeleteJS">
@method('POST')
@csrf
<div class="btn-group" role="group">
<button id="formSubmitClone"
class="btn btn-primary pull-end"
title="{{ __('Sauvegarder le :model', ['model' => Str::singular(__('models.classes.directories'))]) }}"
data-bs-toggle="tooltip">
<i class="fa-solid fa-floppy-disk"></i>
</button>
<button class="btn btn-danger"
title="{{ __('Supprimer le :model', ['model' => Str::singular(__('models.classes.directories'))]) }}"
data-bs-toggle="tooltip">
<i class="fa-solid fa-trash"></i>
</button>
</div>
</form>
</div>
</div>
<div class="d-flex justify-content-between flex-md-nowrap align-items-center flex-wrap pb-3">
<div class="d-flex align-items-start flex-row">
@can('viewAny', $directoryModel)
<a class="btn btn-primary text-decoration-none m-0" data-bs-toggle="tooltip"
href="{{ route('bo.directories.index', ['tenant' => \tenant()]) }}"
title="{{ __('crud.helpers.list_all', ['model' => str(__('models.classes.directory'))->plural()]) }}">
<i class="fa-solid fa-arrow-left"></i>
</a>
@endcan
@include('modules.breadcrumbs.breadcrumb-body', ['brParam' => $directoryModel])
</div>
<div class="card-body bg-light">
<form action="{{ route('bo.directories.update', ['tenant' => \tenant(), 'directory' => $directoryModel]) }}" method="POST">
@csrf
@method('PUT')
@include('bo.pages.directories.form', ['model' => $directoryModel])
<div class="row">
<div class="col text-center">
<button id="formSubmit"
type="submit"
class="btn btn-primary"
title="{{ __('Sauvegarder :model', ['model' => Str::singular(__('models.classes.directories'))]) }}"
data-bs-toggle="tooltip">
{{ __('Sauvegarder') }}
<div class="mb-md-0 mb-2">
@can('delete', $directoryModel)
<form class="confirmDeleteTS" action="{{ route('bo.directories.destroy', ['tenant' => \tenant(), 'directory' => $directoryModel]) }}"
method="POST" novalidate>
@endcan
<div class="btn-group">
@can('duplicate', $directoryModel)
<a class="btn btn-secondary" data-bs-toggle="tooltip"
href="{{ route('bo.directories.duplicate', ['tenant' => \tenant(), 'directory' => $directoryModel]) }}"
title="{{ str(__('crud.actions.duplicate'))->ucFirst() }}">
<i class="fa-solid fa-copy"></i>
</a>
@endcan
@can('update', $directoryModel)
<button class="btn btn-primary" id="formSubmitClone" data-bs-toggle="tooltip" type="submit"
title="{{ __('crud.helpers.save_model', ['model' => __('models.classes.directory')]) }}">
{{ str(__('crud.actions.save'))->ucFirst() }}
</button>
</div>
@endcan
@can('delete', $directoryModel)
@csrf
@method('DELETE')
<button class="btn btn-danger" data-bs-toggle="tooltip" type="submit" title="{{ str(__('crud.actions.delete'))->ucFirst() }}">
<i class="fa-solid fa-trash"></i>
</button>
@endcan
</div>
@can('delete', $directoryModel)
</form>
@endcan
</div>
</div>
@can('update', $directoryModel)
<form action="{{ route('bo.directories.update', ['tenant' => \tenant(), 'directory' => $directoryModel]) }}" method="POST" enctype="multipart/form-data">
@csrf
@method('PUT')
@endcan
@include('bo.pages.directories.form')
@can('update', $directoryModel)
<div class="row">
<div class="col text-center">
<button class="btn btn-primary" id="formSubmit" data-bs-toggle="tooltip" type="submit"
title="{{ __('crud.helpers.save_model', ['model' => __('models.classes.directory')]) }}">
{{ str(__('crud.actions.save'))->ucFirst() }}
</button>
</div>
</div>
</form>
@endcan
@endsection

View file

@ -7,7 +7,7 @@
<div class="col-12 mb-3">
@include('bo.partials.inputs.varchar', [
'id' => 'directoryNameFR',
'label' => __('Nom du :model en français', ['model' => Str::singular(__('models.classes.directories'))]),
'label' => __('Nom du :model en français', ['model' => __('models.classes.directory')]),
'hasWordCounter' => true,
'name' => 'name[fr]',
'value' => $directoryModel->name['fr'] ?? "",
@ -28,7 +28,7 @@
<div class="col-12 mb-3">
@include('bo.partials.inputs.varchar', [
'id' => 'directoryNameEN',
'label' => __('Nom du :model en anglais', ['model' => Str::singular(__('models.classes.directories'))]),
'label' => __('Nom du :model en anglais', ['model' => __('models.classes.directory')]),
'hasWordCounter' => true,
'name' => 'name[en]',
'value' => $directoryModel->name['en'] ?? "",
@ -49,7 +49,7 @@
<div class="col-12 mb-3">
@include('bo.partials.inputs.varchar', [
'id' => 'directoryNameDE',
'label' => __('Nom du :model en allemand', ['model' => Str::singular(__('models.classes.directories'))]),
'label' => __('Nom du :model en allemand', ['model' => __('models.classes.directory')]),
'hasWordCounter' => true,
'name' => 'name[de]',
'value' => $directoryModel->name['de'] ?? "",
@ -70,7 +70,7 @@
<div class="col-12 mb-3">
@include('bo.partials.inputs.varchar', [
'id' => 'directoryNameNL',
'label' => __('Nom du :model en néerlandais', ['model' => Str::singular(__('models.classes.directories'))]),
'label' => __('Nom du :model en néerlandais', ['model' => __('models.classes.directory')]),
'hasWordCounter' => true,
'name' => 'name[nl]',
'value' => $directoryModel->name['nl'] ?? "",

Some files were not shown because too many files have changed in this diff Show more