feat: ✨ adding a field to associate brands with users

This commit is contained in:
Alexis Gousseau 2025-08-05 12:32:21 +02:00
parent fff623144a
commit 8caaff989c
No known key found for this signature in database
GPG key ID: 4A614CB3DBBA2406
6 changed files with 82 additions and 88 deletions

View file

@ -5,11 +5,13 @@ 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\Brand;
use App\Models\User;
use App\Traits\Controllers\UpdateModelPublished;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Uwil\LaravelCommon\Helpers\CollectionHelper;
class UserController extends Controller
{
@ -72,7 +74,8 @@ class UserController extends Controller
*/
public function create(User $user): \Illuminate\Contracts\View\View
{
return view('bo.pages.users.create', ['userModel' => $user]);
$brandModels = CollectionHelper::paginate(Brand::getUserAssociatedBrands(), $this->jsonItemPerPagination);
return view('bo.pages.users.create', ['userModel' => $user, 'brandModels' => $brandModels]);
}
/**
@ -88,6 +91,7 @@ class UserController extends Controller
$user->fill($request->validated());
if ($user->saveOrFail()) {
$user->brands()->sync(collect($request->validated()['brand_id'])->pluck('id')->toArray());
return redirect()->route('bo.users.edit', $user)
->with('success', __('crud.messages.has_been_created', [
'model' => Str::ucfirst(__('models.classes.user'))
@ -108,7 +112,8 @@ class UserController extends Controller
*/
public function edit(User $user): \Illuminate\Contracts\View\View
{
return view('bo.pages.users.edit', ['userModel' => $user]);
$brandModels = CollectionHelper::paginate(Brand::getUserAssociatedBrands(), $this->jsonItemPerPagination);
return view('bo.pages.users.edit', ['userModel' => $user, 'brandModels' => $brandModels]);
}
/**
@ -124,6 +129,7 @@ class UserController extends Controller
$user->fill($request->validated());
if ($user->saveOrFail()) {
$user->brands()->sync(collect($request->validated()['brand_id'])->pluck('id')->toArray());
return redirect()->route('bo.users.edit', $user)
->with('success', __('crud.messages.has_been_updated', [
'model' => Str::ucfirst(__('models.classes.user'))

View file

@ -5,7 +5,6 @@ namespace App\Http\Requests\Bo\Users;
use App\Enums\Users\RoleEnum;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\Enum;
use Illuminate\Validation\Rules\Password;
class StoreUserRequest extends FormRequest
{
@ -27,7 +26,8 @@ class StoreUserRequest extends FormRequest
protected function prepareForValidation(): void
{
$this->merge([
'published' => $this->published ? true : false
'published' => $this->input('published') ? true : false,
'brand_id' => $this->input('brand_id') ?: [],
]);
}
@ -39,19 +39,14 @@ class StoreUserRequest extends FormRequest
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|unique:users,email',
'password' => [
'required', 'required_with:password_confirmation', 'confirmed', 'max:255', Password::min(8)
->letters()
->mixedCase()
->numbers()
->symbols()
->uncompromised()
],
'role' => ['required', new Enum(RoleEnum::class)],
'published' => 'required|boolean',
'first_name' => 'required|string|min:3|max:255',
'last_name' => 'required|string|min:3|max:255',
'email' => 'required|email:rfc,strict,dns,spoof,filter|unique:users,email',
'role' => ['required', new Enum(RoleEnum::class)],
'published' => 'required|boolean',
'brand_id' => 'present|array',
'brand_id.*' => 'sometimes|nullable|array',
'brand_id.*.id' => 'sometimes|nullable|numeric|exists:brands,id',
];
}
@ -63,12 +58,14 @@ class StoreUserRequest extends FormRequest
public function attributes(): array
{
return [
'first_name' => trans('validation.attributes.first_name'),
'last_name' => trans('validation.attributes.last_name'),
'email' => trans('validation.attributes.email'),
'password' => trans('validation.attributes.password'),
'role' => trans('validation.attributes.role'),
'published' => trans('validation.custom.publication')
'first_name' => trans('validation.attributes.first_name'),
'last_name' => trans('validation.attributes.last_name'),
'email' => trans('validation.attributes.email'),
'role' => trans('validation.attributes.role'),
'published' => trans('validation.custom.publication'),
'brand_id' => trans('models.classes.brands'),
'brand_id.*' => trans('models.classes.brands'),
'brand_id.*.id' => trans('models.classes.brands'),
];
}
}

View file

@ -2,8 +2,6 @@
namespace App\Http\Requests\Bo\Users;
use Illuminate\Validation\Rules\Password;
class UpdateUserRequest extends StoreUserRequest
{
/**
@ -14,11 +12,8 @@ class UpdateUserRequest extends StoreUserRequest
protected function prepareForValidation(): void
{
$this->merge([
'published' => $this->published ? true : false,
'password' => $this->password ? $this->password : $this->route('user')->password,
'password_confirmation' => $this->password_confirmation ?
$this->password_confirmation :
$this->route('user')->password
'published' => $this->input('published') ? true : false,
'brand_id' => $this->input('brand_id') ?: [],
]);
}
@ -29,19 +24,15 @@ class UpdateUserRequest extends StoreUserRequest
*/
public function rules(): array
{
/** @var \App\Models\User $userModel */
$userModel = request()->route()->parameter('user');
return array_merge(parent::rules(), [
'email' => [
'required',
'string',
'email:rfc,strict,dns,spoof,filter',
'unique:users,email,' . $this->route('user')->id . ',id',
],
'password' => [
'required', 'required_with:password_confirmation', 'confirmed', 'max:255', Password::min(8)
->letters()
->mixedCase()
->numbers()
->symbols()
->uncompromised()
'unique:users,email,' . $userModel->getKey(),
],
]);
}

View file

@ -212,7 +212,7 @@ class Brand extends Model
*/
public function users(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->belongsToMany(User::class, 'brand_users');
return $this->belongsToMany(Brand::class, 'brand_users', 'brand_id', 'user_id');
}
/**

View file

@ -122,6 +122,6 @@ class User extends Authenticatable
*/
public function brands(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->belongsToMany(Brand::class, 'brand_users');
return $this->belongsToMany(Brand::class, 'brand_users', 'user_id', 'brand_id');
}
}

View file

@ -90,39 +90,6 @@
</div>
</fieldset>
</div>
{{-- PASSWORD --}}
<div class="col-12 mb-3">
<fieldset class="h-100 border p-3">
<legend>{{ __('Mot de passe') }}</legend>
<div class="row">
<div class="col-12 col-md-6 mb-3">
@include('bo.partials.inputs.varchar-password', [
'id' => 'userPassword',
'label' => __('Mot de passe de l\':model', ['model' => __('models.classes.user')]),
'name' => 'password',
'hasWordCounter' => true,
'required' => !$userModel->exists,
'model' => null,
'helper' => __(
'Doît respecter certaines contraintes (8 caractères minimum, lettres, chiffres, caractères spéciaux).'),
])
</div>
<div class="col-12 col-md-6 mb-3">
@include('bo.partials.inputs.varchar-password', [
'id' => 'userPassword_confirmation',
'label' => __('Confirmation du mot de passe de l\':model', [
'model' => __('models.classes.user'),
]),
'name' => 'password_confirmation',
'hasWordCounter' => true,
'required' => !$userModel->exists,
'model' => null,
'helper' => __('Doît correspondre au mot de passe saisi précédemment.'),
])
</div>
</div>
</fieldset>
</div>
{{-- STATUS --}}
<div class="col-12 col-md-6 mb-3">
<fieldset class="h-100 border p-3">
@ -140,6 +107,36 @@
</div>
</fieldset>
</div>
{{-- BRAND --}}
<div class="col-12 col-md-6 pb-3">
<fieldset class="h-100 border p-3" id="userRoleFieldset">
<legend>{{ str(__('models.classes.brands'))->ucfirst() }}</legend>
<div class="row">
@include('bo.partials.inputs.search-belongs-to-dropdown', [
'id' => 'questionBrand',
'label' => __(':model associées', [
'model' => str(__('models.classes.brands'))->ucfirst(),
]),
'name' => 'brand_id',
'relation' => 'brands',
'model' => $userModel,
'targetClass' => 'App\Models\Brand',
'modelListPaginate' => $brandModels ?? [],
'constructedRoute' => route('bo.brands.json-paginate'),
'fieldName' => 'name',
'multiple' => true,
'required' => true,
'disabled' => false,
'placeholder' => __('Rechercher une :model', [
'model' => str(__('models.classes.brands'))->singular(),
]),
'helper' => __('Sélectionner des :model qui seront associées parmis la liste.', [
'model' => __('models.classes.brands'),
]),
])
</div>
</fieldset>
</div>
<div class="row">
<div class="col text-center">
<p><b>{{ __('* Champs obligatoires') }}</b></p>
@ -151,23 +148,26 @@
<script nonce="{{ $nonce }}">
document.addEventListener("DOMContentLoaded", () => {
// Submit button clone.
const submit = document.getElementById('formSubmit')
submitClone = document.getElementById('formSubmitClone');
const submit = document.getElementById('formSubmit');
const submitClone = document.getElementById('formSubmitClone');
submitClone.addEventListener('click', (event) => {
event.preventDefault();
submit.click('');
})
submit.click();
});
// If we edit the password, the confirmation is required.
const passwordInput = document.getElementById('userPassword');
const confirmPasswordInput = document.getElementById('userPassword_confirmation');
passwordInput.addEventListener('input', () => {
if (passwordInput.value !== '') {
confirmPasswordInput.required = true;
} else {
confirmPasswordInput.required = false;
}
})
// If the role is marketing, need brands associated.
const selectRole = document.getElementById('userRole');
const userRoleFieldset = document.getElementById('userRoleFieldset');
selectRole.addEventListener('change', (event) => {
updateBrandsFieldsetStatus(Number(event.target.value));
});
updateBrandsFieldsetStatus(Number("{{ $userModel->role?->value() ?? \App\Enums\Users\RoleEnum::conceptor->value() }}"));
function updateBrandsFieldsetStatus(value) {
const marketingValue = {{ \App\Enums\Users\RoleEnum::marketing->value() }};
const condition = value !== marketingValue;
userRoleFieldset.disabled = condition;
userRoleFieldset.className = `h-100 border p-3 ${condition ? 'd-none' : ''}`;
}
});
</script>
@endpush