add formularies CRUD - issue #12

This commit is contained in:
Alexis Gousseau 2023-07-17 16:39:18 +02:00
parent 8c5f88e926
commit a74eb81674
41 changed files with 4184 additions and 1995 deletions

View file

@ -0,0 +1,41 @@
<?php
namespace App\Enums\Formularies;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* This defines a data type.
*
* @method static self system() input system
* @method static self locked() input locked
* @method static self default() input default
*/
final class InputStatusEnum extends BaseEnumRoutable
{
/**
* enum values
* @return array
*/
protected static function values(): array
{
return [
'system' => 1,
'locked' => 2,
'default' => 3,
];
}
/**
* enum labels
* @return array
*/
protected static function labels(): array
{
return [
'system' => __('system'),
'locked' => __('e-mail'),
'default' => __('default'),
];
}
}

View file

@ -0,0 +1,53 @@
<?php
namespace App\Enums\Formularies;
use Kwaadpepper\Enum\BaseEnumRoutable;
/**
* This defines a data type.
*
* @method static self text() input text
* @method static self email() input email
* @method static self phone() input phone
* @method static self radio() input radio
* @method static self select() input select
* @method static self multipleselect() input multipleselect
* @method static self checkbox() input checkbox
*/
final class InputTypeEnum extends BaseEnumRoutable
{
/**
* enum values
* @return array
*/
protected static function values(): array
{
return [
'text' => 1,
'email' => 2,
'phone' => 3,
'radio' => 4,
'select' => 5,
'multipleselect' => 6,
'checkbox' => 7,
];
}
/**
* enum labels
* @return array
*/
protected static function labels(): array
{
return [
'text' => __('texte'),
'email' => __('e-mail'),
'phone' => __('téléphone'),
'radio' => __('radio'),
'select' => __('liste déroulante'),
'multipleselect' => __('liste déroulante à choix multiple'),
'checkbox' => __('case à cocher'),
];
}
}

View file

@ -48,9 +48,10 @@ class AppUserController extends Controller
$this->sortQuery($app_users);
/** @var integer $pagination Items per page */
$pagination = $request->pagination;
Cache::put('pagination', ($pagination) ? ($pagination) : 12);
$app_users = $app_users->paginate(intval(Cache::get('pagination')));
if ($request->pagination) {
Cache::forever('pagination', intval($request->pagination));
}
$app_users = $app_users->paginate(intval(Cache::get('pagination')) ?? config('pagination.default'));
return view('bo.pages.app-users.index', compact('app_users', 'searchFields'));
}

View file

@ -58,9 +58,10 @@ class DocumentController extends Controller
$this->sortQuery($documents);
/** @var integer $pagination Items per page */
$pagination = $request->pagination;
Cache::put('pagination', ($pagination) ? ($pagination) : 12);
$documents = $documents->paginate(intval(Cache::get('pagination')));
if ($request->pagination) {
Cache::forever('pagination', intval($request->pagination));
}
$documents = $documents->paginate(intval(Cache::get('pagination')) ?? config('pagination.default'));
return view('bo.pages.documents.index', [
'documents' => $documents,

View file

@ -0,0 +1,167 @@
<?php
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;
use App\Models\Formulary;
use App\Traits\Controllers\ChangesModelOrder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class FormularyController extends Controller
{
use ChangesModelOrder;
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function index(Request $request)
{
/** @var \Illuminate\Database\Eloquent\Builder $formularies */
$formularies = Formulary::query();
/** @var string $search Search field */
if ($request->search) {
$this->searchQuery(
$formularies,
$request->search,
null,
'name',
);
}
$searchFields = trans('Nom');
/** Sort columns with a query */
$this->sortQuery($formularies);
/** @var integer $pagination Items per page */
if ($request->pagination) {
Cache::forever('pagination', intval($request->pagination));
}
$formulariesModel = $formularies->paginate(intval(Cache::get('pagination')) ?? config('pagination.default'));
return view('bo.pages.formularies.index', compact('formulariesModel', 'searchFields'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('bo.pages.formularies.create');
}
/**
* Store a newly created resource in storage.
*
* @param \App\Http\Requests\Bo\Formularies\StoreFormularyRequest $request
* @return \Illuminate\Http\Response
*/
public function store(StoreFormularyRequest $request)
{
return DB::transaction(function () use ($request) {
$formulary = new Formulary();
$formulary->fill($request->validated());
if ($formulary->saveOrFail()) {
return redirect()
->route('bo.formularies.edit', $formulary)
->with('success', trans(
':model a été créé',
['model' => Str::singular(trans('models.classes.formularies'))]
));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été créé',
['model' => Str::singular(trans('models.classes.formularies'))]
));
});
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Formulary $formulary
* @return \Illuminate\Http\Response
*/
public function edit(Formulary $formulary)
{
if ($formulary->status->value === InputStatusEnum::system()->value) {
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
]);
}
}
/**
* Update the specified resource in storage.
*
* @param \App\Http\Requests\Bo\Formulary\UpdateFormularyRequest $request
* @param \App\Models\Formulary $formulary
* @return \Illuminate\Http\Response
*/
public function update(UpdateFormularyRequest $request, Formulary $formulary)
{
return DB::transaction(function () use ($request, $formulary) {
$formulary->fill($request->validated());
if ($formulary->saveOrFail()) {
return redirect()
->route('bo.formularies.edit', $formulary)
->with('success', trans(
':model a été mis à jour',
['model' => Str::singular(trans('models.classes.formularies'))]
));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été mis à jour',
['model' => Str::singular(trans('models.classes.formularies'))]
));
});
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Formulary $formulary
* @return \Illuminate\Http\Response
*/
public function destroy(Formulary $formulary)
{
if ($formulary->deleteOrFail()) {
return redirect()
->route('bo.formularies.index')
->with('warning', trans(
':model a bien été supprimé',
['model' => Str::singular(trans('models.classes.formularies'))]
));
}
return redirect()
->back()
->with('error', trans(
':model n\'a pas été supprimé',
['model' => Str::singular(trans('models.classes.formularies'))]
));
}
}

View file

@ -42,9 +42,10 @@ class TagController extends Controller
$this->sortQuery($tags);
/** @var integer $pagination Items per page */
$pagination = $request->pagination;
Cache::put('pagination', ($pagination) ? ($pagination) : 12);
$tags = $tags->paginate(intval(Cache::get('pagination')));
if ($request->pagination) {
Cache::forever('pagination', intval($request->pagination));
}
$tags = $tags->paginate(intval(Cache::get('pagination')) ?? config('pagination.default'));
return view('bo.pages.tags.index', compact('tags', 'searchFields'));
}

View file

@ -0,0 +1,83 @@
<?php
namespace App\Http\Requests\Bo\Formularies;
use App\Enums\Formularies\InputStatusEnum;
use App\Enums\Formularies\InputTypeEnum;
use Illuminate\Foundation\Http\FormRequest;
use Kwaadpepper\Enum\Rules\EnumIsValidRule;
class StoreFormularyRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return boolean
*/
public function authorize()
{
return auth('backend')->check();
}
/**
* Prepare the data for validation.
*
* @return void
*/
protected function prepareForValidation()
{
$this->merge([
'required' => $this->required ? true : false,
'visible' => $this->visible ? true : false,
]);
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$rules = [
'type' => ['required', new EnumIsValidRule(InputTypeEnum::class)],
'placeholder' => 'present|array',
'placeholder.fr' => 'required|string|min:3|max:100',
'placeholder.en' => 'required|string|min:3|max:100',
'placeholder.de' => 'required|string|min:3|max:100',
'placeholder.nl' => 'required|string|min:3|max:100',
'details' => 'nullable|array',
'details.*' => 'required|array',
'details.*.*' => 'sometimes|string|min:3|max:100',
'required' => 'required|boolean',
'visible' => 'required|boolean',
];
if (intval($this->status) !== InputStatusEnum::locked()->value) {
$rules = array_merge($rules, ['name' => 'required|string|min:3|max:70']);
}
return $rules;
}
/**
* Get custom attributes for validator errors.
*
* @return array
*/
public function attributes(): array
{
return [
'type' => trans('type'),
'name' => trans('nom'),
'placeholder' => trans('placeholder'),
'placeholder.fr' => trans('placeholder (FR)'),
'placeholder.en' => trans('placeholder (EN)'),
'placeholder.de' => trans('placeholder (DE) '),
'placeholder.nl' => trans('placeholder (NL)'),
'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'),
'visible' => trans('l\'état de visibilité'),
];
}
}

View file

@ -0,0 +1,19 @@
<?php
namespace App\Http\Requests\Bo\Formularies;
class UpdateFormularyRequest extends StoreFormularyRequest
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
$rules = [
'type' => 'nullable',
];
return \array_merge(parent::rules(), $rules);
}
}

115
app/Models/Formulary.php Normal file
View file

@ -0,0 +1,115 @@
<?php
namespace App\Models;
use App\Enums\Formularies\InputStatusEnum;
use App\Enums\Formularies\InputTypeEnum;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Kwaadpepper\Enum\Traits\CastsEnums;
/**
* @property integer $id
* @property InputTypeEnum $type
* @property string $name
* @property string $placeholder
* @property string $details
* @property boolean $visible
* @property boolean $required
* @property InputStatusEnum $status
*/
class Formulary extends Model
{
use HasFactory;
use CastsEnums;
/**
* fillable
* @var string[]
*/
protected $fillable = [
'type',
'name',
'slug',
'placeholder',
'details',
'visible',
'required',
'status',
];
/**
* @var string[]
*/
protected $casts = [
'placeholder' => 'json',
'details' => 'json',
];
/**
* The attributes that should be cast.
*
* @var array
*/
protected $enumCasts = [
'type' => InputTypeEnum::class,
'status' => InputStatusEnum::class,
];
/**
* The "booted" method of the model.
*
* @return void
*/
protected static function booted(): void
{
static::creating(function (self $model) {
static::setSlug($model);
static::setStatusField($model);
static::setOrder($model);
});
static::updating(function (self $model) {
static::setSlug($model);
});
}
// * METHODS
/**
* Set the slug.
*
* @param self $model
*
* @return void
*/
private static function setSlug(self $model)
{
$model->slug = Str::slug($model->name);
}
/**
* Set the status field.
*
* @param self $model
*
* @return void
*/
private static function setStatusField(self $model)
{
if (!isset($model->status)) {
$model->status = InputStatusEnum::default();
}
}
/**
* Set order after the last element of the list.
*
* @param self $model
* @return void
*/
private static function setOrder(self $model): void
{
$model->order = \intval(self::query()->max('order')) + 1;
}
}

View file

@ -4,6 +4,7 @@ namespace App\Providers;
use App\Models\AppUser;
use App\Models\Document;
use App\Models\Formulary;
use App\Models\Tag;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
@ -47,6 +48,7 @@ class RouteServiceProvider extends ServiceProvider
Route::model('appuser', AppUser::class);
Route::model('document', Document::class);
Route::model('tag', Tag::class);
Route::model('formulary', Formulary::class);
}
/**

View file

@ -24,14 +24,14 @@ trait ChangesModelOrder
$tmp = (\get_class($model))::where('order', $request->direction ? '<' : '>', $model->order)
->orderBy('order', $request->direction ? 'DESC' : 'ASC')->first();
if (!$tmp) {
return redirect()->back()->with('error', __('L\'ordre à été modifié'));
return redirect()->back()->with('error', __('L\'ordre n\'a pu être modifié'));
}
$newOrder = $tmp->order;
$tmp->order = $model->order;
$model->order = $newOrder;
$tmp->saveOrFail();
$model->saveOrFail();
return redirect()->back()->with('success', __('L\'ordre n\'a pu être modifié'));
return redirect()->back()->with('success', __('L\'ordre à été modifié'));
});
}
}

1545
composer.lock generated

File diff suppressed because it is too large Load diff

18
config/pagination.php Normal file
View file

@ -0,0 +1,18 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Default pagination
|--------------------------------------------------------------------------
|
| This option controls the default pagination items per page that will be
| used by the pagination lists on index page.
|
| Supported: a number
|
*/
'default' => env('DEFAULT_PAGINATION', 12),
];

View file

@ -33,7 +33,7 @@ class UpdateAppUsersTable extends Migration
// * OTHERS
Schema::table('app_users', function (Blueprint $table) {
$table->dropUnique('email');
$table->dropUnique('app_users_email_unique');
$table->unique(['email']);
$table->renameColumn('surname', 'lastname');
$table->renameColumn('activated', 'status');

View file

@ -20,7 +20,7 @@ class UpdateSettingsTable extends Migration
DB::statement('ALTER TABLE `settings` CHANGE `updated_at` `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP');
Schema::table('settings', function (Blueprint $table) {
$table->dropUnique('data_key_unique');
$table->dropUnique('settings_key_unique');
$table->unique(['key']);
$table->text('value')->nullable()->default(null)->change();
$table->dateTime('created_at')->default('CURRENT_TIMESTAMP')->nullable()->change();

View file

@ -0,0 +1,40 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateFormulariesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('formularies', function (Blueprint $table) {
$table->id();
$table->integer('type')->comment('The type of the input.');
$table->string('name')->comment('The name of the input.');
$table->string('slug')->unique()->comment('The slug of the input name.');
$table->text('placeholder')->nullable()->comment('Placeholder of the input.');
$table->text('details')->nullable()->comment('Details of the input.');
$table->boolean('visible')->comment('The input is visible or not.');
$table->boolean('required')->comment('The input is required or not.');
$table->boolean('status')->comment('The status of the input.');
$table->integer('order')->comment('Order of this specific input.');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('formularies');
}
}

View file

@ -18,6 +18,7 @@ class DatabaseSeeder extends Seeder
DirectorySeeder::class,
SettingSeeder::class,
TagSeeder::class,
FormularySeeder::class,
]);
}
}

View file

@ -0,0 +1,91 @@
<?php
namespace Database\Seeders;
use App\Enums\Formularies\InputStatusEnum;
use App\Enums\Formularies\InputTypeEnum;
use App\Models\Formulary;
use Illuminate\Database\Seeder;
class FormularySeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$formulary = new Formulary();
$formulary->type = InputTypeEnum::text();
$formulary->name = "firstname";
$formulary->slug = "firstname";
$formulary->placeholder = ["fr" => "Prénom", "en" => "Firstname", "de" => "Vorname", "nl" => "Voornaam"];
$formulary->details = null;
$formulary->required = true;
$formulary->visible = true;
$formulary->status = InputStatusEnum::locked();
$formulary->order = 1;
$formulary->saveOrFail();
$formulary = new Formulary();
$formulary->type = InputTypeEnum::text();
$formulary->name = "lastname";
$formulary->slug = "lastname";
$formulary->placeholder = ["fr" => "Nom", "en" => "Lastname", "de" => "Name", "nl" => "Naam"];
$formulary->details = null;
$formulary->required = true;
$formulary->visible = true;
$formulary->status = InputStatusEnum::locked();
$formulary->order = 2;
$formulary->saveOrFail();
$formulary = new Formulary();
$formulary->type = InputTypeEnum::email();
$formulary->name = "e-mail";
$formulary->slug = "e-mail";
$formulary->placeholder = ["fr" => "E-mail", "en" => "E-mail", "de" => "E-mail", "nl" => "E-mail"];
$formulary->details = null;
$formulary->required = true;
$formulary->visible = true;
$formulary->status = InputStatusEnum::system();
$formulary->order = 3;
$formulary->saveOrFail();
$formulary = new Formulary();
$formulary->type = InputTypeEnum::text();
$formulary->name = "society";
$formulary->slug = "society";
$formulary->placeholder = ["fr" => "Société", "en" => "Society", "de" => "Unternehmen", "nl" => "Bedrijf"];
$formulary->details = null;
$formulary->required = false;
$formulary->visible = true;
$formulary->status = InputStatusEnum::locked();
$formulary->order = 4;
$formulary->saveOrFail();
$formulary = new Formulary();
$formulary->type = InputTypeEnum::text();
$formulary->name = "address";
$formulary->slug = "address";
$formulary->placeholder = ["fr" => "Adresse", "en" => "Address", "de" => "Anschrift", "nl" => "Adres"];
$formulary->details = null;
$formulary->required = false;
$formulary->visible = true;
$formulary->status = InputStatusEnum::locked();
$formulary->order = 5;
$formulary->saveOrFail();
$formulary = new Formulary();
$formulary->type = InputTypeEnum::text();
$formulary->name = "phone";
$formulary->slug = "phone";
$formulary->placeholder = ["fr" => "Téléphone", "en" => "Phone", "de" => "Telefon", "nl" => "Telefoon"];
$formulary->details = null;
$formulary->required = false;
$formulary->visible = true;
$formulary->status = InputStatusEnum::locked();
$formulary->order = 6;
$formulary->saveOrFail();
}
}

2774
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -8,6 +8,7 @@ return [
'documents' => 'Documents',
'users' => 'Users',
'settings' => 'Settings',
'tags' => 'Tags'
'tags' => 'Tags',
'formularies' => 'Formularies',
]
];

View file

@ -4,5 +4,5 @@ return [
"previous" => "Previous page",
"next" => "Next page",
"specific_page" => "Access to page :id",
"current_page" => "Current page"
'paginate_list' => 'Number of items per page',
];

View file

@ -8,6 +8,7 @@ return [
'documents' => 'Documents',
'users' => 'Utilisateurs',
'settings' => 'Paramètres',
'tags' => 'Étiquettes'
'tags' => 'Étiquettes',
'formularies' => 'Formulaires',
]
];

View file

@ -6,6 +6,7 @@
// 3. Include remainder of required Bootstrap stylesheets
@import "~bootstrap/scss/variables";
@import "~bootstrap/scss/variables-dark";
// 4. Include remainder of required parts
@import "~bootstrap/scss/maps";

View file

@ -0,0 +1,282 @@
<template>
<div>
<!-- SELECT INPUT TYPE -->
<fieldset
v-if="!intModel?.type"
class="border h-100 bg-white p-3 mb-3"
>
<legend>{{ __('Type du champs') }}</legend>
<select
id="select-field"
class="form-select"
name="type"
aria-label="Default select example"
@change="updateSelectValue()"
>
<option
:selected="(intModel != null) ? false : true"
disabled
>
{{ __('Sélectionnez un type de champs parmis la liste suivante :') }}
</option>
<template
v-for="(input, inputIndex) in intInputTypes"
:key="inputIndex"
>
<option
:selected="(intModelTypeValue === input.value) ? true : false"
:value="Number(inputIndex) + 1"
>
{{ input.label }}
</option>
</template>
</select>
</fieldset>
<!-- OTHERS FIELDS -->
<template v-if="intSelectValue != null">
<input
class="d-none"
:value="intModel?.status"
name="status"
readonly
>
<fieldset
v-if="intModel?.status !== intInputStatus[0].value &&
intModel?.status !== intInputStatus[1].value"
class="border h-100 bg-white p-3 mb-3"
>
<legend>{{ __('Informations principales') }}</legend>
<div class="row w-100 mx-auto">
<div class="col-12 col-md-6">
<label
for="fieldName"
class="col-form-label"
>
<b>{{ __('Nom du champs') }}</b>
</label>
<WordCounter
id="fieldName"
class="ck-word-count"
/>
<input
id="fieldName"
class="form-control need-word-counter"
type="text"
name="name"
placeholder="Nom du champs"
:value="intModel?.name"
>
<small class="form-text text-muted">{{ __('Doit posséder 3 caractères minimum, 255 maximum.') }}</small>
</div>
</div>
</fieldset>
<!-- PLACEHOLDERS -->
<fieldset
class="border h-100 bg-white p-3 mb-3"
>
<legend>{{ __('Placeholders') }}</legend>
<div class="row w-100 mx-auto">
<div class="col-12 col-md-6">
<label
for="fieldPlaceholderFR"
class="col-form-label"
>
<b>{{ __('Placeholder du champs en FR') }}</b>
</label>
<WordCounter
id="fieldPlaceholderFR"
class="ck-word-count"
/>
<input
id="fieldPlaceholderFR"
class="form-control need-word-counter"
type="text"
name="placeholder[fr]"
placeholder="Placeholder en français"
:value="(intPlaceholder) ? intPlaceholder['fr' as unknown as number] : ''"
>
<p class="text-danger m-0">
{{ parseValidationInput(`details.fr`, allErrors) }}
</p>
<small class="form-text text-muted">{{ __('Doit posséder 3 caractères minimum, 255 maximum.') }}</small>
</div>
<div class="col-12 col-md-6">
<label
for="fieldPlaceholderEN"
class="col-form-label"
>
<b>{{ __('Placeholder du champs en EN') }}</b>
</label>
<WordCounter
id="fieldPlaceholderEN"
class="ck-word-count"
/>
<input
id="fieldPlaceholderEN"
class="form-control need-word-counter"
type="text"
name="placeholder[en]"
placeholder="Placeholder en anglais"
:value="(intPlaceholder) ? intPlaceholder['en' as unknown as number] : ''"
>
<p class="text-danger m-0">
{{ parseValidationInput(`details.en`, allErrors) }}
</p>
<small class="form-text text-muted">{{ __('Doit posséder 3 caractères minimum, 255 maximum.') }}</small>
</div>
<div class="col-12 col-md-6">
<label
for="fieldPlaceholderDE"
class="col-form-label"
>
<b>{{ __('Placeholder du champs en DE') }}</b>
</label>
<WordCounter
id="fieldPlaceholderDE"
class="ck-word-count"
/>
<input
id="fieldPlaceholderDE"
class="form-control need-word-counter"
type="text"
name="placeholder[de]"
placeholder="Placeholder en allemand"
:value="(intPlaceholder) ? intPlaceholder['de' as unknown as number] : ''"
>
<p class="text-danger m-0">
{{ parseValidationInput(`details.de`, allErrors) }}
</p>
<small class="form-text text-muted">{{ __('Doit posséder 3 caractères minimum, 255 maximum.') }}</small>
</div>
<div class="col-12 col-md-6">
<label
for="fieldPlaceholderNL"
class="col-form-label"
>
<b>{{ __('Placeholder du champs en NL') }}</b>
</label>
<WordCounter
id="fieldPlaceholderNL"
class="ck-word-count"
/>
<input
id="fieldPlaceholderNL"
class="form-control need-word-counter"
type="text"
name="placeholder[nl]"
placeholder="Placeholder en néerlandais"
:value="(intPlaceholder) ? intPlaceholder['nl' as unknown as number] : ''"
>
<p class="text-danger m-0">
{{ parseValidationInput(`details.nl`, allErrors) }}
</p>
<small class="form-text text-muted">{{ __('Doit posséder 3 caractères minimum, 255 maximum.') }}</small>
</div>
</div>
</fieldset>
<!-- DETAILS CONTENT -->
<fieldset
v-if="intSelectValue === intInputTypes[3].value ||
intSelectValue === intInputTypes[4].value ||
intSelectValue === intInputTypes[5].value ||
intSelectValue === intInputTypes[6].value"
class="border h-100 bg-white p-3 mb-3"
>
<legend>{{ __('Détails du contenu') }}</legend>
<div class="row w-100 mx-auto">
<div class="col-12">
<label
class="form-check-label"
for="fieldRequired"
role="button"
>
<b>{{ __('Liste des éléments') }}</b>
</label>
<ListItemComponent
:itemlist="(intModel?.details) ? intModel?.details : []"
:itemcolumns="[['fr', 'Valeur en FR'], ['en', 'Valeur en EN'], ['de', 'Valeur en DE'], ['nl', 'Valeur en NL']]"
:itemtype="['text','text', 'text', 'text']"
:itemlimit="[1,10]"
inputname="details"
:inputplaceholder="[
'Saisir un élement en français',
'Saisir un élement en anglais',
'Saisir un élement en allemand',
'Saisir un élement en néerlandais'
]"
:header="false"
:errors="allErrors"
/>
</div>
</div>
</fieldset>
<!-- SWITCH BUTTONS -->
<FormulariesSwitchComponent
:model="intModel"
/>
</template>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import trans from "../../modules/trans";
import parseLaravelErrorMessages from "./../../modules/parseLaravelErrorMessages";
import FormulariesSwitchComponent from "./FormulariesSwitchComponent.vue";
import ListItemComponent from "./ListItemComponent.vue";
import WordCounter from "./WordCounterComponent.vue";
export default defineComponent({
mixins: [trans, parseLaravelErrorMessages],
components: {
FormulariesSwitchComponent,
ListItemComponent,
WordCounter,
},
data(): {
intId: string;
intInputTypes: {
value: number,
label: string,
}[];
intInputStatus: {
value: number,
label: string,
}[];
intSelectValue: Number | null;
intModel: LaravelModel | null;
intModelTypeValue: number;
intPlaceholder: [] | null;
allErrors: Record<string, string[]>;
} {
return {
intId: "",
intInputTypes: [],
intInputStatus: [],
intSelectValue: null,
intModel: null,
intModelTypeValue: 0,
intPlaceholder: [],
allErrors: {},
};
},
mounted() {
const json = String(this.$attrs.json ?? "{}");
const data = JSON.parse(json);
this.intId = data.id;
this.intInputTypes = data.inputType;
this.intInputStatus = data.inputStatus;
this.intModel = data.model;
this.intModelTypeValue = this.intModel?.type as number;
this.intSelectValue = this.intModelTypeValue;
this.intPlaceholder = (this.intModel != null) ? this.intModel.placeholder as [] : null;
this.allErrors = data.errors ?? {};
},
methods: {
updateSelectValue() {
const intSelect = document.getElementById("select-field") as HTMLSelectElement;
this.intSelectValue = Number(intSelect.value);
},
},
});
</script>

View file

@ -0,0 +1,101 @@
<template>
<fieldset class="border h-100 bg-white p-3 mb-3">
<legend>{{ __('Spécificités') }}</legend>
<div class="row w-100 mx-auto">
<div class="col-12 col-md-6">
<div class="form-check form-switch">
<input
@change="updateVisibleValue()"
id="fieldVisible"
class="form-check-input"
name="visible"
type="checkbox"
value="1"
role="button"
ref="inputVisible"
:checked="(intModel?.visible) ? true : false"
>
<label
class="form-check-label"
for="fieldVisible"
role="button"
>
<b>{{ __('État de visibilité du champs') }}</b>
</label>
<br>
<small class="form-text text-muted">{{ __('Le champs est visible ou non.') }}</small>
</div>
</div>
<div
v-if="displayRequiredField"
class="col-12 col-md-6"
>
<div class="form-check form-switch">
<input
id="fieldRequired"
class="form-check-input"
name="required"
type="checkbox"
value="1"
role="button"
:checked="(intModel?.required) ? true : false"
>
<label
class="form-check-label"
for="fieldRequired"
role="button"
>
<b>{{ __('État d\'obligation du champs') }}</b>
</label>
<br>
<small class="form-text text-muted">{{ __('Le champs est obligatoire ou non.') }}</small>
</div>
</div>
</div>
</fieldset>
</template>
<script lang="ts">
import { defineComponent, PropType } from "vue";
import trans from "../../modules/trans";
export default defineComponent({
mixins: [trans],
props: {
id: {
type: String,
default: null,
},
model: {
type: Object as PropType<LaravelModel>,
default: null,
},
},
data(): {
intId: string;
intModel: LaravelModel | null;
displayRequiredField: boolean;
} {
return {
intId: "",
intModel: null,
displayRequiredField: false,
};
},
mounted() {
const json = String(this.$attrs.json ?? "{}");
const data = JSON.parse(json);
this.intId = data.id ?? String(this.id);
this.intModel = data.model ?? this.model;
this.$nextTick(() => {
this.updateVisibleValue();
});
},
methods: {
updateVisibleValue() {
const inputVisible = this.$refs.inputVisible as HTMLInputElement;
this.displayRequiredField = (inputVisible.checked) ? true : false;
}
},
});
</script>

View file

@ -0,0 +1,405 @@
<template>
<div :class="`list-item-component list-item-${intInputName}`">
<table
v-if="intItemList.length > 0"
class="table text-center table-sm table-hover table-bordered m-0"
>
<thead>
<tr>
<th
v-if="displayHeader"
scope="col"
class="table-action"
>
<button
v-if="hasHeader"
@click.prevent="removeAllHeaders()"
class="btn btn-sm btn-danger"
:title="__('Supprimer toutes les en-têtes')"
data-bs-tooltip="tooltip"
>
<FontAwesomeIcon icon="fa-solid fa-minus" />
</button>
</th>
<th
v-if="displayHeader && displayHeaders.includes(true)"
class="col-6 align-middle"
>
{{ __("En-tête") }}
({{ countHeaders }})
</th>
<th
v-for="(column, columnIndex) of intItemColumns"
:key="columnIndex"
class="align-middle"
:class="displayHeaders.includes(true) ? 'col-6' : ''"
scope="col"
>
{{ column[1] }}
({{ intItemList.length }})
</th>
<th
scope="col"
v-if="displayBtnRemove"
class="table-action"
>
<button
v-if="intItemList.length > 1"
@click.prevent="removeAllLines()"
class="btn btn-sm btn-danger"
:title="__('Supprimer toutes les lignes')"
data-bs-tooltip="tooltip"
>
<FontAwesomeIcon icon="fa-solid fa-trash" />
</button>
</th>
</tr>
</thead>
<tbody class="table-group-divider">
<tr
v-for="(item, itemIndex) of intItemList"
:key="itemIndex"
:class="displayHeaders[itemIndex] ? 'header' : ''"
>
<td
v-if="displayHeader"
class="align-bottom"
scope="row"
>
<button
v-if="!displayHeaders[itemIndex]"
@click.prevent="updateStatusHeader(itemIndex)"
class="btn btn-sm btn-primary"
:title="__('Ajouter une en-tête')"
data-bs-tooltip="tooltip"
>
<FontAwesomeIcon icon="fa-solid fa-heading" />
</button>
<button
v-if="displayHeaders[itemIndex]"
@click.prevent="updateStatusHeader(itemIndex)"
class="btn btn-sm btn-danger"
:title="__('Supprimer cette en-tête')"
data-bs-tooltip="tooltip"
>
<FontAwesomeIcon icon="fa-solid fa-minus" />
</button>
</td>
<td
v-if="displayHeaders.includes(true)"
class="align-bottom"
scope="row"
>
<div
v-if="displayHeaders[itemIndex]"
class="d-flex align-items-center border-0"
>
<input
class="form-control form-control-sm"
:name="`${intInputName}[${String(itemIndex)}][header]`"
type="text"
:placeholder="__('Recette d\'un cookie au chocolat')"
v-model="item['header']"
>
<FontAwesomeIcon
icon="fa-solid fa-angles-right"
class="mx-2"
/>
</div>
</td>
<td
class="align-bottom"
v-for="(column, columnIndex) of intItemColumns"
:key="columnIndex"
scope="row"
>
<p class="text-danger m-0">
{{
parseValidationInput(
`${intInputName}.${String(itemIndex)}.${column[0]}`,
allErrors
)
}}
</p>
<input
class="form-control form-control-sm"
:name="`${intInputName}[${String(itemIndex)}][${column[0]}]`"
:type="intItemType[columnIndex]"
v-model="item[column[0]]"
:placeholder="intInputPlaceholder[columnIndex]"
:min="intItemType[columnIndex] == 'number' ? '0' : undefined"
>
</td>
<td
v-if="displayBtnRemove"
class="align-bottom"
scope="row"
>
<button
@click.prevent="removeLine(itemIndex)"
class="btn btn-sm btn-danger"
:title="__('Supprimer cette ligne')"
data-bs-tooltip="tooltip"
>
<FontAwesomeIcon icon="fa-solid fa-trash" />
</button>
</td>
</tr>
</tbody>
</table>
<div v-else>
<p>{{ __("Aucun élément n'a été saisi.") }}</p>
<input
class="form-control form-control-sm"
:name="`${intInputName}`"
type="hidden"
value=""
>
</div>
<div
v-if="displayBtnAdd"
class="text-center mt-1"
>
<button
@click.prevent="addLine"
class="btn btn-sm btn-primary"
type="button"
:title="__('Ajouter une ligne')"
data-bs-tooltip="tooltip"
>
<FontAwesomeIcon icon="fa-solid fa-plus" />
</button>
</div>
</div>
</template>
<script lang="ts">
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import { Tooltip } from "bootstrap";
import { defineComponent } from "vue";
import parseLaravelErrorMessages from "./../../modules/parseLaravelErrorMessages";
import trans from "./../../modules/trans";
export default defineComponent({
name: "ListItemComponent",
components: {
FontAwesomeIcon,
},
mixins: [trans, parseLaravelErrorMessages],
props: {
itemlist: {
type: Array<Record<string,string>>,
default: [],
},
itemlimit: {
type: Array<number>,
default: [],
},
itemcolumns: {
type: Array<Array<string>>,
default: "",
},
itemtype: {
type: Array<string>,
default: "",
},
inputname: {
type: String,
default: "",
},
inputplaceholder: {
type: Array<string>,
default: "",
},
header: {
type: Boolean,
default: false,
},
errors: {
type: Object,
default: null,
},
},
data(): {
intItemList: Array<Record<string,string>>;
intItemColumns: string[][];
intItemLimit: number[];
intInputName: string;
intItemType: string[];
intInputPlaceholder: string[];
intMin: number;
intMax: number | null;
allErrors: Record<string, string[]>;
displayBtnAdd: boolean;
displayBtnRemove: boolean;
displayHeader: boolean;
displayHeaders: Array<boolean>;
tooltipList: HTMLButtonElement[];
} {
return {
intItemList: [],
intItemColumns: [[""]],
intItemLimit: [0, 0],
intInputName: "",
intItemType: [""],
intInputPlaceholder: [""],
intMin: 0,
intMax: null,
allErrors: {},
displayBtnAdd: true,
displayBtnRemove: true,
displayHeader: false,
displayHeaders: [],
tooltipList: [],
};
},
computed : {
hasHeader() {
return this.displayHeaders.filter((header : boolean) => {
return header;
}).length > 1 && this.intItemList.length > 1;
},
countHeaders() {
return this.displayHeaders.filter((header: boolean) => {
return header;
}).length;
}
},
mounted() {
const json = String(this.$attrs.json ?? "{}");
const data = JSON.parse(json);
this.intItemList = this.itemlist ?? data.itemList;
this.intItemColumns = this.itemcolumns ?? data.itemColumns;
this.intItemType = this.itemtype ?? data.itemType;
this.intItemLimit = this.itemlimit ?? data.itemLimit;
this.intInputName = this.inputname ?? data.inputName;
this.intInputPlaceholder = this.inputplaceholder ?? data.inputPlaceholder;
this.displayHeader = this.header ?? data.header;
// * Min and max calc.
this.intMin = (this.intItemLimit ?? [])[0] ?? 0;
this.intMin = this.intMin < 0 ? 0 : this.intMin;
this.intMax = (this.intItemLimit ?? [])[1] ?? 0;
this.intMax = this.intMax <= this.intMin ? null : this.intMax;
this.allErrors = this.errors ?? data.errors ?? {};
this.setDefaultLines();
this.updateComponent();
},
methods: {
/**
* Set default line(s)
* according to the minimum limit defined.
*/
setDefaultLines() {
if (this.intItemList.length < 1) {
for (let index = 0; index < this.intMin; index++) {
this.addLine();
}
} else {
this.intItemList.forEach((item: { header?: string }, key) => {
if (item.header) {
this.displayHeaders[key] = true;
}
});
}
},
/**
* Add a new line to the <table>,
* add a new element to the array,
* show remove line button.
*/
addLine() {
this.intItemList.push({});
this.displayHeaders.push(false);
this.displayBtnRemove == false ? (this.displayBtnRemove = true) : "";
this.updateComponent();
},
removeAllHeaders() {
this.displayHeaders = [];
this.updateComponent();
},
updateStatusHeader(itemIndex: number) {
this.displayHeaders[itemIndex] = this.displayHeaders[itemIndex]
? false
: true;
this.updateComponent();
},
updateComponent() {
this.checkLengthList();
this.$nextTick(() => {
this.updateBootstrapTooltip();
});
},
removeAllLines() {
this.intItemList.length = 0;
this.removeAllHeaders();
this.addLine();
this.updateComponent();
},
/**
* Remove a specific line from the <table>,
* remove a specific element to the array,
* show adding line button.
*/
removeLine(itemIndex: number) {
this.intItemList.splice(itemIndex, 1);
this.displayHeaders.splice(itemIndex, 1);
!this.displayBtnAdd ? (this.displayBtnAdd = true) : "";
this.intItemList.length <= 0 ? (this.intItemList = []) : "";
this.updateComponent();
},
/**
* Check the length of the list,
* update variables accordingly.
*/
checkLengthList() {
/** Length minimum */
if (this.intItemList.length <= this.intMin) {
this.displayBtnRemove = false;
}
/** Length maximum */
if (this.intMax !== null && this.intItemList.length >= this.intMax) {
this.displayBtnAdd = false;
}
},
/**
* Update Bootstrap tooltips.
*/
updateBootstrapTooltip() {
let newTooltipList = [].slice.call(
document.querySelectorAll(
".list-item-" + this.intInputName + " [data-bs-tooltip=\"tooltip\"]"
)
) as HTMLButtonElement[];
let tmp = newTooltipList.filter((x) => !this.tooltipList.includes(x));
tmp.map((tooltip) => {
return new Tooltip(tooltip);
});
this.closeBootstrapTooltip();
this.tooltipList = newTooltipList;
},
/**
* Close all Bootstrap tooltips.
*/
closeBootstrapTooltip() {
this.tooltipList.forEach((tooltip) => {
tooltip.blur();
Tooltip.getInstance(tooltip)?.hide();
});
},
},
});
</script>
<style lang="scss">
.list-item-component {
.table {
table-layout: fixed;
&-action {
width: 2.5rem;
}
}
.header {
border-top: 2px solid black;
}
}
</style>

View file

@ -38,9 +38,9 @@ export default {
allErrors: Record<string, Array<string>> = {}
): string | null {
let errorList = [] as Array<string>;
Object.keys(allErrors).filter((value, index) => {
Object.keys(allErrors).filter((value) => {
if (value == inputName) {
errorList = allErrors[index];
errorList = allErrors[value];
}
});
return errorList.length

View file

@ -43,6 +43,8 @@ import {
faVideo,
faFile,
faListUl,
faHeading,
faAnglesRight,
} from "@fortawesome/free-solid-svg-icons";
config.autoReplaceSvg = false;
@ -85,6 +87,8 @@ library.add(faFloppyDisk);
library.add(faFile);
library.add(faVideo);
library.add(faListUl);
library.add(faHeading);
library.add(faAnglesRight);
// Front
library.add(faChevronLeft);

View file

@ -40,6 +40,14 @@
{{ __(':model', ['model' => __('models.classes.tags')]) }}
</a>
</li>
<li class="nav-item">
<a class="nav-link @if(request()->routeIs('bo.formularies.*')) active @endif"
href="{{ route('bo.formularies.index') }}"
title="{{ __('Liste des champs du :model', ['model' => Str::singular(__('models.classes.formularies'))]) }}"
data-bs-toggle="tooltip">
{{ __(':model', ['model' => Str::singular(__('models.classes.formularies'))]) }}
</a>
</li>
<li class="nav-item">
<a class="nav-link @if(request()->routeIs('bo.settings.*')) active @endif"
href="{{ route('bo.settings.index') }}"

View file

@ -15,12 +15,14 @@
</button>
<ul class="dropdown-menu">
@foreach (\App\Enums\Pagination\ItemsPerPaginationEnum::toArray() as $itemsPerPaginationEnum)
@if ($itemsPerPaginationEnum->value < $paginator->total())
<li>
<a class="dropdown-item text-center"
href="{{ request()->fullUrlWithQuery(['pagination' => $itemsPerPaginationEnum->value]) }}">
{{ $itemsPerPaginationEnum->value }}
</a>
</li>
@endif
@endforeach
</ul>
</div>

View file

@ -46,7 +46,9 @@
<script nonce="{{ $nonce }}">
document.getElementById('searchDelete').addEventListener('click', function (e) {
document.getElementById('searchField').value = '';
document.getElementById('searchTag').selectedIndex = 0;
if (document.getElementById('searchTag')) {
document.getElementById('searchTag').selectedIndex = 0;
}
document.getElementById('searchForm').submit();
});
</script>

View file

@ -1,5 +1,10 @@
@php
View::share('noOrder', $noOrder);
$route = request()->route();
$routeName = $route->getName();
$rst = !is_null(request()->rst) || !Session::has("$routeName.sorted");
$noOrder = !empty($noOrder);
$noOrder = $noOrder || (Session::get("$routeName.sort_col") !== 'order' and (Session::has("$routeName.sort_col") or Session::has("$routeName.sort_way")));
$noOrder = $noOrder || !empty(request()->search);
$ignore = (isset($ignore) and is_array($ignore)) ? $ignore : [];
@endphp
<tr>
@ -12,14 +17,13 @@ $ignore = (isset($ignore) and is_array($ignore)) ? $ignore : [];
@endphp
{{-- NO Sort for order column --}}
@if($col === 'order' or in_array($col, $ignore))
@if(!$noOrder or $rst)
<th scope="col" class="text-center @if(isset($mobileHide) and is_array($mobileHide) and in_array($col, $mobileHide)) d-none d-md-table-cell text-center @endif">
<span>{{ $colname }}</span>
@if($noOrder and !in_array($col, $ignore)) @continue @endif
<th scope="col" class="text-center @if(isset($mobileHide) and is_array($mobileHide) and in_array($col, $mobileHide)) d-none d-md-table-cell @endif">
<span class="text-nowrap">{{ $colname }}</span>
</th>
@endif
@continue
@endif
<th scope="col" class="text-center @if(isset($mobileHide) and is_array($mobileHide) and in_array($col, $mobileHide)) d-none d-md-table-cell text-center @endif">
<th scope="col" class="text-center @if(isset($mobileHide) and is_array($mobileHide) and in_array($col, $mobileHide)) d-none d-md-table-cell @endif">
<span class="text-nowrap">
{{ $colname }}
</span>
@ -30,7 +34,7 @@ $ignore = (isset($ignore) and is_array($ignore)) ? $ignore : [];
class="btn btn-sm btn-outline-dark"
title="{{ __('Trier :name dans l\'ordre croissant', ['name' => Str::lower($colname)]) }}"
data-bs-toggle="tooltip">
<i class="fas fa-arrow-up 2xs"></i>
<i class="fas fa-arrow-down 2xs"></i>
</button>
</a>
@endif
@ -41,15 +45,15 @@ $ignore = (isset($ignore) and is_array($ignore)) ? $ignore : [];
class="btn btn-sm btn-outline-dark"
title="{{ __('Trier :name dans l\'ordre décroissant', ['name' => Str::lower($colname)]) }}"
data-bs-toggle="tooltip">
<i class="fas fa-arrow-down 2xs"></i>
<i class="fas fa-arrow-up 2xs"></i>
</button>
</a>
@endif
</th>
@endforeach
{{-- Buttons Col --}}
<th class="text-end" scope="col">
@if($noOrder and !$rst)
<th scope="col" class="text-end">
@if(!$rst)
<a class="btn btn-sm btn-danger"
href="{{ request()->fullUrlWithQuery(['sort_col' => null, 'sort_way' => null, 'rst' => true, 'search' => null, 'tag' => null]) }}"
data-bs-toggle="tooltip"

View file

@ -2,10 +2,6 @@
<table class="table fix-action">
<thead>
@php
$rst = !is_null(request()->rst);
$routeName = request()->route()->getName();
$noOrder = Session::get("$routeName.sort_col") !== 'order' and (Session::has("$routeName.sort_col") or Session::has("$routeName.sort_way"));
$noOrder = Session::get("$routeName.sort_col") !== '' and (Session::has("$routeName.sort_col") or Session::has("$routeName.sort_way"));
$cols = ['lastname' => __('Nom'), 'firstname' => __('Prénom'), 'email' => __('Email'), 'order' => __('Order')];
@endphp
@include('bo.modules.table-col-sorter', [
@ -26,7 +22,8 @@
<td class="text-center">
<span>{{ $app_usersModel->email }}</span>
</td>
@if(!$noOrder or $rst)
@php $routeName = request()->route()->getName(); @endphp
@if(empty(request()->search) && Session::get("$routeName.sort_col") === "order")
<td class="align-middle d-flex justify-content-center align-items-center">
<form action="{{ route('bo.app-users.change-order', ['appuser' => $app_usersModel, 'action' => 'up']) }}"
method="POST"

View file

@ -2,9 +2,6 @@
<table class="table fix-action">
<thead>
@php
$rst = !is_null(request()->rst);
$routeName = request()->route()->getName();
$noOrder = Session::get("$routeName.sort_col") !== '' and (Session::has("$routeName.sort_col") or Session::has("$routeName.sort_way"));
$cols = ['title' => __('Title'), 'directory_id' => __('Dossier'), 'document_id' => __('Document'), 'type' => __('Type'), 'order' => __('Order')];
@endphp
@include('bo.modules.table-col-sorter', [
@ -59,7 +56,8 @@
@break
@endswitch
</td>
@if(!$noOrder or $rst)
@php $routeName = request()->route()->getName(); @endphp
@if(empty(request()->search) && Session::get("$routeName.sort_col") === "order")
<td class="align-middle d-flex justify-content-center align-items-center">
<form action="{{ route('bo.documents.change-order', ['document' => $documentModel, 'action' => 'up']) }}"
method="POST"

View file

@ -0,0 +1,53 @@
@extends('bo.layout')
@section('title', sprintf('%s - %s', Str::singular(__('models.classes.formularies')), Str::ucfirst(__('création'))))
@section('description', __('Page de création d\'un nouveau champs poour le formulaire de contact.'))
@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.formularies.index') }}"
class="btn btn-primary text-decoration-none m-0"
data-bs="tooltip"
data-bs-placement="top"
title="{{ __('Retourner sur la liste des champs du :model', ['model' => Str::singular(__('models.classes.formularies'))]) }}"
data-bs-toggle="tooltip">
<i class="fa-solid fa-arrow-left"></i>
</a>
<span class="fw-bold">{{ Str::singular(__('models.classes.formularies')) }}</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 le champs du :model', ['model' => Str::singular(__('models.classes.formularies'))]) }}"
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.formularies.store') }}" method="POST">
@csrf
@include('bo.pages.formularies.form')
<div class="row">
<div class="col text-center">
<button id="formSubmit"
type="submit"
class="btn btn-primary"
title="{{ __('Sauvegarder le champs du :model', ['model' => Str::singular(__('models.classes.formularies'))]) }}"
data-bs-toggle="tooltip">
{{ __('Sauvegarder') }}
</button>
</div>
</div>
</form>
</div>
</div>
@endsection

View file

@ -0,0 +1,75 @@
@extends('bo.layout', ['brParam' => $formularyModel])
@section('title', sprintf('%s - %s %s', Str::singular(__('models.classes.formularies')), Str::ucfirst(__('édition')), $formularyModel->lastname))
@section('description', __('Page d\'édition d\'un nouveau champs poour le formulaire de contact.'))
@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.formularies.index') }}"
class="btn btn-primary text-decoration-none m-0"
data-bs="tooltip"
data-bs-placement="top"
title="{{ __('Retourner sur la liste des champs du :model', ['model' => Str::singular(__('models.classes.formularies'))]) }}"
data-bs-toggle="tooltip">
<i class="fa-solid fa-arrow-left"></i>
</a>
<span class="fw-bold">{{ Str::singular(__('models.classes.formularies')) }}</span>&nbsp;<small class="text-muted">{{ Str::ucfirst(__('édition')) }}</small>
</h1>
</div>
@if (!$formularyModel->locked)
<div class="col-12 col-md-4 d-flex justify-content-center justify-content-md-end align-items-center">
@if ($formularyModel->status->value !== \App\Enums\Formularies\InputStatusEnum::system()->value &&
$formularyModel->status->value !== \App\Enums\Formularies\InputStatusEnum::locked()->value)
<form action="{{ route('bo.formularies.destroy', ['formulary' => $formularyModel->id]) }}" method="POST" class="d-inline confirmDeleteJS">
@method('DELETE')
@csrf
@endif
<div class="btn-group" role="group">
<button id="formSubmitClone"
class="btn btn-primary pull-end"
title="{{ __('Sauvegarder le champs du :model', ['model' => Str::singular(__('models.classes.formularies'))]) }}"
data-bs-toggle="tooltip">
<i class="fa-solid fa-floppy-disk"></i>
</button>
@if ($formularyModel->status->value !== \App\Enums\Formularies\InputStatusEnum::system()->value &&
$formularyModel->status->value !== \App\Enums\Formularies\InputStatusEnum::locked()->value)
<button class="btn btn-danger"
title="{{ __('Supprimer le champs du :model', ['model' => Str::singular(__('models.classes.formularies'))]) }}"
data-bs-toggle="tooltip">
<i class="fa-solid fa-trash"></i>
</button>
</div>
</form>
@endif
</div>
@endif
</div>
</div>
<div class="card-body bg-light">
<form action="{{ route('bo.formularies.update', $formularyModel->id) }}" method="POST">
@csrf
@method('PUT')
@include('bo.pages.formularies.form', ['model' => $formularyModel])
@if (!$formularyModel->locked)
<div class="row">
<div class="col text-center">
<button id="formSubmit"
type="submit"
class="btn btn-primary"
title="{{ __('Sauvegarder le champs du :model', ['model' => Str::singular(__('models.classes.formularies'))]) }}"
data-bs-toggle="tooltip">
{{ __('Sauvegarder') }}
</button>
</div>
</div>
@endif
</form>
</div>
</div>
@endsection

View file

@ -0,0 +1,29 @@
<div class="row">
{{-- SELECT INPUT COMPONENT --}}
<div class="col-12 mb-3">
@php
$data = [
'id' => 'FormulariesSelectInput',
'inputType' => \App\Enums\Formularies\InputTypeEnum::toArray(),
'inputStatus' => \App\Enums\Formularies\InputStatusEnum::toArray(),
'model' => $formularyModel ?? null,
'errors' => $errors->getBag('default')->getMessages() ?? []
];
@endphp
<div class="formularies-select-input" data-json='@json($data)'></div>
</div>
</div>
@push('scripts')
<script nonce="{{ $nonce }}">
document.addEventListener("DOMContentLoaded", () => {
// Submit button clone.
const submit = document.getElementById('formSubmit')
submitClone = document.getElementById('formSubmitClone');
submitClone.addEventListener('click', (event) => {
event.preventDefault();
submit.click('');
})
});
</script>
@endpush

View file

@ -0,0 +1,115 @@
<div class="table-responsive">
<table class="table fix-action">
<thead>
@php
$cols = [
'type' => __('Type'),
'name' => __('Nom'),
'required' => __('Requis'),
'visible' => __('Visible'),
'order' => __('Ordre')
];
@endphp
@include('bo.modules.table-col-sorter', [
'cols' => $cols,
'mobileHide' => [],
])
</thead>
<tbody>
@if ($formulariesModel->count())
@foreach ($formulariesModel as $formularyModel)
<tr>
<td class="text-center">
<span>{{ $formularyModel->type->label }}</span>
</td>
<td class="text-center">
<span>{{ $formularyModel->name }}</span>
</td>
<td class="text-center">
@if ($formularyModel->required)
<i class="fa-solid fa-circle-check text-primary"></i>
@else
<i class="fa-solid fa-circle-xmark text-danger"></i>
@endif
</td>
<td class="text-center">
@if ($formularyModel->visible)
<i class="fa-solid fa-circle-check text-primary"></i>
@else
<i class="fa-solid fa-circle-xmark text-danger"></i>
@endif
</td>
@php $routeName = request()->route()->getName(); @endphp
@if(empty(request()->search) && Session::get("$routeName.sort_col") === "order")
<td class="align-middle d-flex justify-content-center align-items-center">
<form action="{{ route('bo.formularies.change-order', ['formulary' => $formularyModel, 'action' => 'up']) }}"
method="POST"
class="text-center @if($loop->first and $formulariesModel->onFirstPage()) invisible @endif">
@csrf
<button type="submit"
class="btn btn-sm btn-outline-dark @if(($loop->first and $formulariesModel->onFirstPage())) disabled @endif"
title="{{ __('Modifier l\'ordre d\'apparition vers le haut') }}"
data-bs-toggle="tooltip">
<i class="fas fa-arrow-up"></i>
</button>
</form>
<form action="{{ route('bo.formularies.change-order', ['formulary' => $formularyModel, 'action' => 'down']) }}"
method="POST"
class="text-center @if($loop->last and $formulariesModel->currentPage() === $formulariesModel->lastPage()) invisible @endif">
@csrf
<button type="submit"
class="btn btn-sm btn-outline-dark ms-1 @if($loop->last and $formulariesModel->currentPage() === $formulariesModel->lastPage()) disabled @endif"
title="{{ __('Modifier l\'ordre d\'apparition vers le bas') }}"
data-bs-toggle="tooltip">
<i class="fas fa-arrow-down"></i>
</button>
</form>
</td>
@endif
<td class="text-end">
@if ($formularyModel->status->value !== \App\Enums\Formularies\InputStatusEnum::system()->value)
<div class="btn-group">
@if ($formularyModel->status->value !== \App\Enums\Formularies\InputStatusEnum::locked()->value)
<form action="{{ route('bo.formularies.destroy', $formularyModel->id) }}" method="POST" class="d-inline confirmDeleteJS">
@method('DELETE')
@csrf
@endif
<div class="btn-group" role="group">
<a id="formSubmitClone"
href="{{ route('bo.formularies.edit', $formularyModel) }}"
class="btn btn-sm btn-primary"
role="button"
title="{{ __('Modifier le champs du :model', ['model' => Str::singular(__('models.classes.formularies'))]) }}"
data-bs-toggle="tooltip">
<i class="fa-solid fa-pencil"></i>
</a>
@if ($formularyModel->status->value !== \App\Enums\Formularies\InputStatusEnum::locked()->value)
<button class="btn btn-sm btn-danger"
title="{{ __('Supprimer le champs du :model', ['model' => Str::singular(__('models.classes.formularies'))]) }}"
data-bs-toggle="tooltip">
<i class="fa-solid fa-trash"></i>
</button>
</div>
</form>
@endif
</div>
@else
<span data-bs-toggle="tooltip" data-bs-placement="top" title="{{ __('Ce champs est obligatoire, seul son ordre est éditable') }}">
<i class="fas fa-lock"></i>
</span>
@endif
</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

@ -0,0 +1,37 @@
@extends('bo.layout')
@section('title', sprintf('%s - %s', __('models.classes.formularies'), 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">{{ Str::singular(__('models.classes.formularies')) }}</span>&nbsp;<small class="text-muted">{{ Str::ucfirst(__('liste')) }}</small>
</h1>
</div>
<div class="col d-flex justify-content-end align-items-center">
<a href="{{ route('bo.formularies.create') }}"
class="btn btn-primary"
title="{{ __('Créer un champs du :model', ['model' => Str::singular(__('models.classes.formularies'))]) }}"
data-bs-toggle="tooltip">
<i class="fa-solid fa-plus"></i>
</a>
</div>
</div>
</div>
<div class="card-body">
@include('bo.modules.search-bar')
<div class="row mt-3">
<div class="col">
@include('bo.pages.formularies.index-list')
</div>
</div>
{{ $formulariesModel->withQueryString()->onEachSide(1)->links() }}
</div>
</div>
@endsection

View file

@ -1,6 +1,7 @@
<?php
use App\Http\Controllers\Bo\AppUserController;
use App\Http\Controllers\Bo\FormularyController;
use App\Http\Controllers\Bo\DashboardController;
use App\Http\Controllers\Bo\DirectoriesController;
use App\Http\Controllers\Bo\DocumentController;
@ -60,9 +61,14 @@ Route::prefix('bo')->name('bo.')->middleware(['admin-secure'])->group(function (
// * SETTINGS
Route::resource('settings', SettingController::class)->except('show');
// * FORMULARIES
Route::resource('formularies', FormularyController::class)->except('show');
Route::post('/formularies/{formulary}/change-order', [FormularyController::class, 'changeOrder'])
->name('formularies.change-order');
});
});
Route::get('/', function(){
Route::get('/', function () {
return redirect()->route('bo.dashboard');
});
});

View file

@ -71,26 +71,24 @@ mix.clean({
// * COMMON
mix.vue({
runtimeOnly: true,
extractStyles: "css/v-extract.css",
globalStyles: false,
})
.eslint({
fix: true,
extensions: ["js", "ts"],
})
mix.sass("resources/sass/bo/bo.scss", "css")
.ts("resources/ts/bo.ts", "js", { transpileOnly: true })
.stylelint({
configFile: ".stylelintrc.json",
files: ["**/*.scss"],
})
.eslint({
fix: true,
extensions: ["js", "ts"],
})
.vue({
runtimeOnly: true,
extractStyles: "css/v-extract.css",
globalStyles: false,
})
.polyfill(polyfill)
.version()
.sourceMaps(false, "inline-source-map")
// * BACKEND
.ts("resources/ts/bo.ts", "js", { transpileOnly: true })
.sass("resources/sass/bo/bo.scss", "css");
.sourceMaps(true, "inline-source-map");
// * Copy Blade Images Assets
mix.copyDirectory("resources/assets", "public/build/assets");