feat: ✨ add notices crud #117
This commit is contained in:
parent
e803c2c60f
commit
c2b4da5ad3
24 changed files with 1487 additions and 31 deletions
57
app/Enums/NoticeTypeEnum.php
Normal file
57
app/Enums/NoticeTypeEnum.php
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
namespace App\Enums;
|
||||
|
||||
use Uwil\LaravelCommon\Traits\Enums\BaseEnum;
|
||||
|
||||
/**
|
||||
* Defines the available information types.
|
||||
*/
|
||||
enum NoticeTypeEnum: int
|
||||
{
|
||||
use BaseEnum;
|
||||
|
||||
case primary = 1;
|
||||
case secondary = 2;
|
||||
case success = 3;
|
||||
case danger = 4;
|
||||
case warning = 5;
|
||||
case info = 6;
|
||||
case light = 7;
|
||||
case dark = 8;
|
||||
|
||||
/**
|
||||
* Optionnal labels definition
|
||||
*/
|
||||
private const CLASSES = [
|
||||
self::primary->name => 'bg-primary text-primary-foreground',
|
||||
self::secondary->name => 'bg-secondary text-secondary-foreground',
|
||||
self::success->name => 'bg-successive text-successive-foreground',
|
||||
self::danger->name => 'bg-destructive text-destructive-foreground',
|
||||
self::warning->name => 'bg-preventive text-preventive-foreground',
|
||||
self::info->name => 'bg-informative text-informative-foreground',
|
||||
self::light->name => 'bg-muted text-muted-foreground',
|
||||
self::dark->name => 'bg-foreground text-background',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get Class.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function class(): string
|
||||
{
|
||||
return self::CLASSES[$this->name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get enum classes.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public static function toClasses(): array
|
||||
{
|
||||
return self::CLASSES;
|
||||
}
|
||||
}
|
||||
|
||||
245
app/Http/Controllers/Back/NoticeController.php
Normal file
245
app/Http/Controllers/Back/NoticeController.php
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Back;
|
||||
|
||||
use App\Http\Requests\Back\Notices\StoreNoticeRequest;
|
||||
use App\Http\Requests\Back\Notices\UpdateNoticeRequest;
|
||||
use App\Models\Notice;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Uwil\LaravelBackend\Controllers\BackendController;
|
||||
use Uwil\LaravelBackend\Resources\CommonResource;
|
||||
|
||||
/**
|
||||
* Handles CRUD operations for Notice in the backend.
|
||||
*/
|
||||
class NoticeController extends BackendController
|
||||
{
|
||||
/**
|
||||
* Create the controller instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->authorizeResource(Notice::class);
|
||||
$this->modelName = Notice::modelName();
|
||||
$this->modelPage = Notice::modelPage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Inertia\Response
|
||||
*/
|
||||
public function index(Request $request): \Inertia\Response
|
||||
{
|
||||
/** @var \Illuminate\Database\Eloquent\Builder<\App\Models\Notice> $query */
|
||||
$query = Notice::query();
|
||||
|
||||
if ($request->routeIs("back.$this->modelName.trash")) {
|
||||
$query = $query->onlyTrashed();
|
||||
}
|
||||
|
||||
/** @var string $search Search field */
|
||||
$search = $request->search;
|
||||
if ($search) {
|
||||
$this->searchQuery(
|
||||
$query,
|
||||
$search,
|
||||
null,
|
||||
// TODO: Fields here.
|
||||
);
|
||||
}
|
||||
$searchFields = \implode(', ', [
|
||||
// TODO: Translations here.
|
||||
]);
|
||||
|
||||
/** Sort columns with a query */
|
||||
$this->sortQuery($query);
|
||||
|
||||
/** Custom pagination */
|
||||
$models = $this->paginate($query);
|
||||
|
||||
return $this->inertiaRender("back/Pages/$this->modelPage/Index", [
|
||||
'models' => CommonResource::collection($models),
|
||||
'search' => $search,
|
||||
'searchFields' => $searchFields,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*
|
||||
* @param \App\Models\Notice $notice
|
||||
* @return \Inertia\Response
|
||||
*/
|
||||
public function create(Notice $notice): \Inertia\Response
|
||||
{
|
||||
return $this->inertiaRender("back/Pages/$this->modelPage/Create", [
|
||||
'data' => CommonResource::make($notice),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a specific resource.
|
||||
*
|
||||
* @param \App\Models\Notice $notice
|
||||
* @return \Inertia\Response
|
||||
*/
|
||||
public function show(Notice $notice): \Inertia\Response
|
||||
{
|
||||
return $this->inertiaRender("back/Pages/$this->modelPage/Show", [
|
||||
'data' => CommonResource::make($notice),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource.
|
||||
*
|
||||
* @param \App\Http\Requests\Back\Notices\StoreNoticeRequest $request
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function store(StoreNoticeRequest $request): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
return DB::transaction(function () use ($request) {
|
||||
$notice = new Notice();
|
||||
$notice->inertiaFill($request->validated());
|
||||
|
||||
if ($notice->saveOrFail()) {
|
||||
return redirect()->route("back.$this->modelName.edit", $notice)
|
||||
->with('success', trans('uwil-backend::crud.messages.has_been_created', [
|
||||
'model' => Str::ucfirst(trans_choice("models.classes.$this->modelName", 1)),
|
||||
]));
|
||||
}
|
||||
return redirect()->back()
|
||||
->with('error', trans('uwil-backend::crud.messages.cannot_be_created', [
|
||||
'model' => Str::ucfirst(trans_choice("models.classes.$this->modelName", 1)),
|
||||
]));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*
|
||||
* @param \App\Models\Notice $notice
|
||||
* @return \Inertia\Response
|
||||
*/
|
||||
public function edit(Notice $notice): \Inertia\Response
|
||||
{
|
||||
return $this->inertiaRender("back/Pages/$this->modelPage/Edit", [
|
||||
'data' => CommonResource::make($notice),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource.
|
||||
*
|
||||
* @param \App\Http\Requests\Back\Notices\UpdateNoticeRequest $request
|
||||
* @param \App\Models\Notice $notice
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function update(
|
||||
UpdateNoticeRequest $request,
|
||||
Notice $notice
|
||||
): \Illuminate\Http\RedirectResponse {
|
||||
return DB::transaction(function () use ($request, $notice) {
|
||||
$notice->inertiaFill($request->validated());
|
||||
|
||||
if ($notice->saveOrFail()) {
|
||||
return redirect()->route("back.$this->modelName.edit", $notice)
|
||||
->with('success', trans('uwil-backend::crud.messages.has_been_updated', [
|
||||
'model' => Str::ucfirst(trans_choice("models.classes.$this->modelName", 1)),
|
||||
]));
|
||||
}
|
||||
return redirect()->route("back.$this->modelName.edit", $notice)
|
||||
->with('error', trans('uwil-backend::crud.messages.cannot_be_updated', [
|
||||
'model' => Str::ucfirst(trans_choice("models.classes.$this->modelName", 1)),
|
||||
]));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicate the specified resource.
|
||||
*
|
||||
* @param \App\Models\Notice $notice
|
||||
* @return \Inertia\Response
|
||||
*/
|
||||
public function duplicate(Notice $notice): \Inertia\Response
|
||||
{
|
||||
return $this->create($notice->replicate());
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore the specified resource.
|
||||
*
|
||||
* @param integer $notice
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function restore(int $notice): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$notice = Notice::withTrashed()->findOrFail($notice);
|
||||
/** @var array<string,string> */
|
||||
$previousQueries = [];
|
||||
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
|
||||
if ($notice->restore()) {
|
||||
return redirect()->route("back.$this->modelName.trash", $previousQueries)
|
||||
->with('success', trans('uwil-backend::crud.messages.has_been_restored', [
|
||||
'model' => Str::ucfirst(trans_choice("models.classes.$this->modelName", 1)),
|
||||
]));
|
||||
}
|
||||
return redirect()->back()
|
||||
->with('error', trans('uwil-backend::crud.messages.cannot_be_restored', [
|
||||
'model' => Str::ucfirst(trans_choice("models.classes.$this->modelName", 1)),
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the specified resource.
|
||||
*
|
||||
* @param \App\Models\Notice $notice
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function destroy(Notice $notice): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
/** @var array<string,string> */
|
||||
$previousQueries = [];
|
||||
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
|
||||
if ($notice->delete()) {
|
||||
return redirect()->route("back.$this->modelName.index", $previousQueries)
|
||||
->with('success', trans('uwil-backend::crud.messages.has_been_deleted', [
|
||||
'model' => Str::ucfirst(trans_choice("models.classes.$this->modelName", 1)),
|
||||
]));
|
||||
}
|
||||
return redirect()->back()
|
||||
->with('error', trans('uwil-backend::crud.messages.cannot_be_deleted', [
|
||||
'model' => Str::ucfirst(trans_choice("models.classes.$this->modelName", 1)),
|
||||
]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the specified resource.
|
||||
*
|
||||
* @param \App\Models\Notice|integer $notice
|
||||
* @return \Illuminate\Http\RedirectResponse
|
||||
*/
|
||||
public function forceDestroy(Notice|int $notice): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
if (is_int($notice)) {
|
||||
$notice = Notice::withTrashed()->findOrFail($notice);
|
||||
}
|
||||
/** @var array<string,string> */
|
||||
$previousQueries = [];
|
||||
\parse_str(\parse_url(url()->previous(), \PHP_URL_QUERY), $previousQueries);
|
||||
if ($notice->forceDelete()) {
|
||||
return redirect()->route("back.$this->modelName.trash", $previousQueries)
|
||||
->with('success', trans('uwil-backend::crud.messages.has_been_deleted', [
|
||||
'model' => Str::ucfirst(trans_choice("models.classes.$this->modelName", 1)),
|
||||
]));
|
||||
}
|
||||
return redirect()->back()
|
||||
->with('error', trans('uwil-backend::crud.messages.cannot_be_deleted', [
|
||||
'model' => Str::ucfirst(trans_choice("models.classes.$this->modelName", 1)),
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,10 @@
|
|||
|
||||
namespace App\Http\Middlewares;
|
||||
|
||||
use App\Enums\NoticeTypeEnum;
|
||||
use App\Models\Notice;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Inertia\Middleware;
|
||||
use Uwil\LaravelCommon\Helpers\InertiaHelper;
|
||||
use Uwil\LaravelCommon\Support\InertiaSharedRegistry;
|
||||
|
|
@ -19,7 +22,16 @@ class HandleInertiaRequests extends Middleware
|
|||
{
|
||||
$props = [
|
||||
...InertiaHelper::errorProps(parent::share($request)),
|
||||
// More props here.
|
||||
'auth' => [
|
||||
'policies' => [
|
||||
'notices' => [
|
||||
'viewAny' => Gate::allows('viewAny', Notice::class),
|
||||
'viewTrash' => Gate::allows('viewTrash', Notice::class),
|
||||
'create' => Gate::allows('create', Notice::class),
|
||||
]
|
||||
]
|
||||
],
|
||||
'noticeTypeEnum' => NoticeTypeEnum::toArray(),
|
||||
];
|
||||
return array_replace_recursive(
|
||||
$props,
|
||||
|
|
|
|||
54
app/Http/Requests/Back/Notices/StoreNoticeRequest.php
Normal file
54
app/Http/Requests/Back/Notices/StoreNoticeRequest.php
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Back\Notices;
|
||||
|
||||
use App\Enums\NoticeTypeEnum;
|
||||
use App\Models\Notice;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Validation\Rules\Enum;
|
||||
use Uwil\LaravelBackend\Rules\WysiwygLength;
|
||||
|
||||
/**
|
||||
* Handles authorization and validation for creating a new Notice.
|
||||
*/
|
||||
class StoreNoticeRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return Gate::check('create', Notice::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'title' => 'required|string|min:3|max:255',
|
||||
'message' => ['required', 'string', new WysiwygLength()],
|
||||
'type' => ['required', 'integer', new Enum(NoticeTypeEnum::class)],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get custom attributes for validator errors.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'title' => trans('validation.custom.title'),
|
||||
'message' => trans('validation.custom.message'),
|
||||
'type' => trans('validation.custom.type'),
|
||||
];
|
||||
}
|
||||
}
|
||||
21
app/Http/Requests/Back/Notices/UpdateNoticeRequest.php
Normal file
21
app/Http/Requests/Back/Notices/UpdateNoticeRequest.php
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Requests\Back\Notices;
|
||||
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
/**
|
||||
* Handles authorization and validation for updating an existing Notice.
|
||||
*/
|
||||
class UpdateNoticeRequest extends StoreNoticeRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return Gate::check('update', $this->route('notice'));
|
||||
}
|
||||
}
|
||||
99
app/Models/Notice.php
Normal file
99
app/Models/Notice.php
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\NoticeTypeEnum;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Uwil\LaravelActivityLogs\Traits\HasActivityLog;
|
||||
use Uwil\LaravelBackend\Traits\Models\HasCache;
|
||||
use Uwil\LaravelBackend\Traits\Models\HasModelNaming;
|
||||
use Uwil\LaravelBackend\Traits\Models\HasUwilImages;
|
||||
use Uwil\LaravelI18n\Traits\Models\HasTranslations;
|
||||
|
||||
/**
|
||||
* @property integer $id Id of the notice.
|
||||
* @property string $title Title of the notice.
|
||||
* @property string $message Message of the notice.
|
||||
* @property integer $type Type of the notice.
|
||||
* @property-read \Illuminate\Support\Carbon $created_at Date the notice password was created.
|
||||
* @property-read \Illuminate\Support\Carbon $updated_at Date the notice password was updated.
|
||||
* @property-read \Illuminate\Support\Carbon $deleted_at Date the notice password was deleted.
|
||||
*/
|
||||
class Notice extends Model
|
||||
{
|
||||
use HasActivityLog;
|
||||
use HasCache;
|
||||
use HasFactory;
|
||||
use HasModelNaming;
|
||||
use HasTranslations;
|
||||
use HasUwilImages;
|
||||
use SoftDeletes;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'title',
|
||||
'message',
|
||||
'type',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'type' => NoticeTypeEnum::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* The translatable columns.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
public $translatable = [
|
||||
'title',
|
||||
'message',
|
||||
];
|
||||
|
||||
/**
|
||||
* Relations should be included in the cache.
|
||||
*
|
||||
* @var list<string>
|
||||
*/
|
||||
protected static $cacheRelations = [
|
||||
// TODO: Relations here.
|
||||
];
|
||||
|
||||
/**
|
||||
* Perform any actions required after the model boots.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function booted(): void
|
||||
{
|
||||
static::creating(function (self $model) {
|
||||
// TODO: Methods here.
|
||||
});
|
||||
static::updating(function (self $model) {
|
||||
// TODO: Methods here.
|
||||
});
|
||||
static::updated(function (self $model) {
|
||||
// TODO: Methods here.
|
||||
});
|
||||
static::restoring(function (self $model) {
|
||||
// TODO: Methods here.
|
||||
});
|
||||
static::deleted(function (self $model) {
|
||||
// TODO: Methods here.
|
||||
});
|
||||
static::forceDeleted(function (self $model) {
|
||||
// TODO: Methods here.
|
||||
});
|
||||
}
|
||||
}
|
||||
112
app/Policies/NoticePolicy.php
Normal file
112
app/Policies/NoticePolicy.php
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Uwil\LaravelBackend\Policies\Rules\UserStaticRules;
|
||||
use Uwil\LaravelBackend\Support\UserRoleRegistry;
|
||||
|
||||
/**
|
||||
* Handles authorization for Notice model actions.
|
||||
*/
|
||||
class NoticePolicy
|
||||
{
|
||||
/**
|
||||
* Determine whether the user can view any models.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $authModel
|
||||
* @return boolean
|
||||
*/
|
||||
public function viewAny(Model $authModel): bool
|
||||
{
|
||||
return UserStaticRules::atLeastRole($authModel, UserRoleRegistry::get('client')->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can view trash models.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $authModel
|
||||
* @return boolean
|
||||
*/
|
||||
public function viewTrash(Model $authModel): bool
|
||||
{
|
||||
return UserStaticRules::atLeastRole($authModel, UserRoleRegistry::get('client')->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can view the model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $authModel
|
||||
* @return boolean
|
||||
*/
|
||||
public function view(Model $authModel): bool
|
||||
{
|
||||
return UserStaticRules::atLeastRole($authModel, UserRoleRegistry::get('client')->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can create models.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $authModel
|
||||
* @return boolean
|
||||
*/
|
||||
public function create(Model $authModel): bool
|
||||
{
|
||||
return UserStaticRules::atLeastRole($authModel, UserRoleRegistry::get('client')->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can update the model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $authModel
|
||||
* @return boolean
|
||||
*/
|
||||
public function update(Model $authModel): bool
|
||||
{
|
||||
return UserStaticRules::atLeastRole($authModel, UserRoleRegistry::get('client')->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can duplicate the model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $authModel
|
||||
* @return boolean
|
||||
*/
|
||||
public function duplicate(Model $authModel): bool
|
||||
{
|
||||
return UserStaticRules::atLeastRole($authModel, UserRoleRegistry::get('client')->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can restore the model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $authModel
|
||||
* @return boolean
|
||||
*/
|
||||
public function restore(Model $authModel): bool
|
||||
{
|
||||
return UserStaticRules::atLeastRole($authModel, UserRoleRegistry::get('client')->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can soft delete the model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $authModel
|
||||
* @return boolean
|
||||
*/
|
||||
public function delete(Model $authModel): bool
|
||||
{
|
||||
return UserStaticRules::atLeastRole($authModel, UserRoleRegistry::get('client')->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can permanently delete the model.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Model $authModel
|
||||
* @return boolean
|
||||
*/
|
||||
public function forceDelete(Model $authModel): bool
|
||||
{
|
||||
return UserStaticRules::atLeastRole($authModel, UserRoleRegistry::get('client')->value);
|
||||
}
|
||||
}
|
||||
34
database/factories/NoticeFactory.php
Normal file
34
database/factories/NoticeFactory.php
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Enums\NoticeTypeEnum;
|
||||
use App\Models\Notice;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
/**
|
||||
* Factory for generating Notice model instances with randomized data.
|
||||
*/
|
||||
class NoticeFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* Associated model.
|
||||
*
|
||||
* @var class-string<\App\Models\Notice>
|
||||
*/
|
||||
protected $model = Notice::class;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
"title" => fake()->word(),
|
||||
"message" => sprintf("<p>%s</p>", fake()->text(\rand(20, 40))),
|
||||
"type" => fake()->randomElement(NoticeTypeEnum::cases()),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ return new class extends Migration
|
|||
public function up(): void
|
||||
{
|
||||
Schema::create('user_passwords', function (Blueprint $table): void {
|
||||
$table->id()->comment('Id of the user password.');;
|
||||
$table->id()->comment('Id of the user password.');
|
||||
$table->foreignId('user_id')->unique()->comment('Associated user.')
|
||||
->constrained('users')->cascadeOnDelete();
|
||||
$table->string('password')->comment('Password of the user.');
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('notices', function (Blueprint $table) {
|
||||
$table->id()->comment('Id of the notice.');
|
||||
$table->string('title')->comment('Title of the notice.');
|
||||
$table->text('message')->comment('Message of the notice.');
|
||||
$table->unsignedTinyInteger('type')->comment('Type of the notice.');
|
||||
$table->timestamp('created_at')->nullable()->comment('Date the notice password was created.');
|
||||
$table->timestamp('updated_at')->nullable()->comment('Date the notice password was updated.');
|
||||
$table->timestamp('deleted_at')->nullable()->comment('Date the notice password was deleted.');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('notices');
|
||||
}
|
||||
};
|
||||
25
database/seeders/Classic/NoticeSeeder.php
Normal file
25
database/seeders/Classic/NoticeSeeder.php
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders\Classic;
|
||||
|
||||
use App\Models\Notice;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
/**
|
||||
* Seeds the notices table.
|
||||
*/
|
||||
class NoticeSeeder extends Seeder
|
||||
{
|
||||
/** @var string $table Associated table to this seeder. */
|
||||
public static string $table = 'notices';
|
||||
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
Notice::factory(10)->create();
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,6 @@ declare(strict_types=1);
|
|||
|
||||
return [
|
||||
"classes" => [
|
||||
// "users" => "utilisateur|utilisateurs",
|
||||
"notices" => "information|informations",
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -6,5 +6,8 @@ return [
|
|||
"custom" => [
|
||||
"token" => "token",
|
||||
"new_password" => "nouveau mot de passe",
|
||||
"title" => "titre",
|
||||
"message" => "message",
|
||||
"type" => "type",
|
||||
],
|
||||
];
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
// * Packages
|
||||
import { InfoIcon } from "lucide-react";
|
||||
import { route } from "ziggy-js";
|
||||
import { Link } from "@inertiajs/react";
|
||||
// * Components shadcn
|
||||
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
|
||||
import { Button } from "@/components/ui/button";
|
||||
// * Other
|
||||
import {
|
||||
filterActions,
|
||||
NavListLinksProps,
|
||||
} from "@/back/Components/Layout/Sidebar/AppSidebar";
|
||||
import { SidebarGroupLink } from "@/back/Components/Layout/Sidebar/SidebarRegistry";
|
||||
import { AuthProp } from "@/back/types/global";
|
||||
import str from "@/hooks/use-string";
|
||||
import { GetTranslation } from "@/lib/utils";
|
||||
import useTrans from "@/hooks/use-translations";
|
||||
import Views from "@/back/lib/actions";
|
||||
|
||||
export default function SidebarLinkNotices(auth: AuthProp): SidebarGroupLink {
|
||||
const modelName = "notices";
|
||||
|
||||
// Translations.
|
||||
const transModel = GetTranslation(`models.classes.${modelName}`, {
|
||||
choice: 2,
|
||||
});
|
||||
const transCreate = str(
|
||||
useTrans(`uwil-backend::crud.actions.${Views.CREATION.label}`),
|
||||
)
|
||||
.ucFirst()
|
||||
.value();
|
||||
const transArchives = str(useTrans("uwil-backend::back.archives"))
|
||||
.ucFirst()
|
||||
.value();
|
||||
|
||||
const link: NavListLinksProps = {
|
||||
title: str(transModel).ucFirst().value(),
|
||||
model: modelName,
|
||||
icon: InfoIcon,
|
||||
show: auth.policies.notices.viewAny,
|
||||
actions: (model: string) =>
|
||||
filterActions({
|
||||
auth,
|
||||
model,
|
||||
items: [
|
||||
auth.policies.notices.create && (
|
||||
<DropdownMenuItem key="create" className="p-0">
|
||||
<Button variant="ghost" asChild>
|
||||
<Link href={route(`back.${model}.create`)} className="w-full">
|
||||
{/* eslint-disable-next-line react/jsx-pascal-case */}
|
||||
<Views.CREATION.icon />
|
||||
{transCreate}
|
||||
</Link>
|
||||
</Button>
|
||||
</DropdownMenuItem>
|
||||
),
|
||||
auth.policies.notices.viewTrash && (
|
||||
<DropdownMenuItem className="p-0">
|
||||
<Button variant="ghost" asChild>
|
||||
<Link href={route(`back.${model}.trash`)} className="w-full">
|
||||
{/* eslint-disable-next-line react/jsx-pascal-case */}
|
||||
<Views.ARCHIVATION.icon />
|
||||
{transArchives}
|
||||
</Link>
|
||||
</Button>
|
||||
</DropdownMenuItem>
|
||||
),
|
||||
],
|
||||
}),
|
||||
};
|
||||
|
||||
return { link, group: "modules" };
|
||||
}
|
||||
|
|
@ -2,27 +2,22 @@
|
|||
import { usePage } from "@inertiajs/react";
|
||||
import { ReactNode } from "react";
|
||||
// * Other
|
||||
import { AppEnv } from "@/back/types/global";
|
||||
import { AppEnv } from "@/types/global";
|
||||
import { TranslationField } from "@/back/types/i18n";
|
||||
import useTrans from "@/hooks/use-translations";
|
||||
import str from "@/hooks/use-string";
|
||||
|
||||
const PREVIEW_LENGTH = 50;
|
||||
|
||||
function normalizePreview(value: string): string {
|
||||
return str(value).normalize().value().substring(0, PREVIEW_LENGTH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the best translation value for the current locale.
|
||||
*
|
||||
* Priority:
|
||||
* 1. Value for the current app locale
|
||||
* 2. First non-empty available translation (fallback)
|
||||
*
|
||||
* @param value - Translation field containing localized values
|
||||
* @return An object with the resolved value and optional fallback locale,
|
||||
* or null if no translation is available
|
||||
*/
|
||||
function useGetTranslationValue(
|
||||
value?: TranslationField,
|
||||
): { value: string; locale?: string } | null {
|
||||
const { appEnv } = usePage().props as unknown as {
|
||||
appEnv: AppEnv;
|
||||
};
|
||||
const { appEnv } = usePage().props as unknown as { appEnv: AppEnv };
|
||||
|
||||
if (!value) return null;
|
||||
|
||||
const local = value[appEnv.locale];
|
||||
|
|
@ -34,20 +29,23 @@ function useGetTranslationValue(
|
|||
return { value: fallback[1], locale: fallback[0] };
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a table cell displaying a translated value.
|
||||
*
|
||||
* - Displays the value directly if it matches the current locale
|
||||
* - Displays "locale: value" when a fallback translation is used
|
||||
* - Displays a "not known" message when no translation is available
|
||||
*
|
||||
* @param value - Translation field containing localized values
|
||||
* @return A ReactNode ready to be rendered inside a table cell
|
||||
*/
|
||||
function RenderValue({ value, renderHtml }: { value: string; renderHtml: boolean }): ReactNode {
|
||||
const preview = normalizePreview(value);
|
||||
|
||||
return renderHtml ? (
|
||||
// eslint-disable-next-line react/no-danger
|
||||
<p dangerouslySetInnerHTML={{ __html: preview }} />
|
||||
) : (
|
||||
preview
|
||||
);
|
||||
}
|
||||
|
||||
export default function RenderTranslationCell({
|
||||
value,
|
||||
renderHtml = false,
|
||||
}: {
|
||||
value?: TranslationField;
|
||||
renderHtml?: boolean;
|
||||
}): ReactNode {
|
||||
const result = useGetTranslationValue(value);
|
||||
const notKnown = useTrans("uwil-i18n::trans.value_not_known");
|
||||
|
|
@ -57,13 +55,13 @@ export default function RenderTranslationCell({
|
|||
}
|
||||
|
||||
if (!result.locale) {
|
||||
return result.value;
|
||||
return <RenderValue value={result.value} renderHtml={renderHtml} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="italic text-xs">
|
||||
<span className="text-muted-foreground mr-1">{result.locale}:</span>
|
||||
{result.value}
|
||||
<RenderValue value={result.value} renderHtml={renderHtml} />
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
63
resources/ts/back/Pages/Notices/Create.tsx
Normal file
63
resources/ts/back/Pages/Notices/Create.tsx
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
// * Packages
|
||||
import { usePage } from "@inertiajs/react";
|
||||
import { route } from "ziggy-js";
|
||||
import { ArrowLeftIcon } from "lucide-react";
|
||||
// * Other
|
||||
import AuthenticatedLayout from "@/back/Layouts/AuthenticatedLayout";
|
||||
import Form from "@/back/Pages/Notices/Form";
|
||||
import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip";
|
||||
import useTrans from "@/hooks/use-translations";
|
||||
import { Actions, Methods } from "@/back/lib/enums";
|
||||
import { AuthProp, DefaultModel, SingleResource } from "@/back/types/global";
|
||||
|
||||
export default function Create({
|
||||
data,
|
||||
modelName,
|
||||
}: {
|
||||
data: SingleResource;
|
||||
modelName: string;
|
||||
}) {
|
||||
const { viewAny } = (usePage().props.auth as AuthProp).policies.notices;
|
||||
const model = data.data as DefaultModel;
|
||||
|
||||
// Translations.
|
||||
const transBackToList = useTrans("uwil-backend::back.crud_back_to_list");
|
||||
const transTitle = useTrans(
|
||||
route().current(`back.${modelName}.duplicate`)
|
||||
? "uwil-backend::crud.helpers.duplication_model"
|
||||
: "uwil-backend::crud.helpers.creation_model",
|
||||
{
|
||||
params: {
|
||||
model: useTrans(`models.classes.${modelName}`, {
|
||||
choice: 1,
|
||||
}),
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const beforeNode = () => {
|
||||
return (
|
||||
viewAny && (
|
||||
<div className="flex justify-end items-center gap-x-1.5">
|
||||
<BtnOptionnalLinkWithTooltip
|
||||
link={route(`back.${modelName}.index`)}
|
||||
content={<ArrowLeftIcon />}
|
||||
tooltipContent={<p>{transBackToList}</p>}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthenticatedLayout title={transTitle} beforeNode={beforeNode()}>
|
||||
<Form
|
||||
model={model}
|
||||
modelName={modelName}
|
||||
modelRoute={modelName}
|
||||
action={Actions.STORE}
|
||||
method={Methods.POST}
|
||||
/>
|
||||
</AuthenticatedLayout>
|
||||
);
|
||||
}
|
||||
71
resources/ts/back/Pages/Notices/Edit.tsx
Normal file
71
resources/ts/back/Pages/Notices/Edit.tsx
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
// * Packages
|
||||
import { usePage } from "@inertiajs/react";
|
||||
import { route } from "ziggy-js";
|
||||
import { ArrowLeftIcon } from "lucide-react";
|
||||
// * Other
|
||||
import AuthenticatedLayout from "@/back/Layouts/AuthenticatedLayout";
|
||||
import ModelActions from "@/back/Components/ModelActions";
|
||||
import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip";
|
||||
import Form from "@/back/Pages/Notices/Form";
|
||||
import useTrans from "@/hooks/use-translations";
|
||||
import { Actions, Methods } from "@/back/lib/enums";
|
||||
import { Views } from "@/back/lib/actions";
|
||||
import { AuthProp, DefaultModel, SingleResource } from "@/back/types/global";
|
||||
|
||||
export default function Edit({
|
||||
data,
|
||||
modelName,
|
||||
}: {
|
||||
data: SingleResource;
|
||||
modelName: string;
|
||||
}) {
|
||||
const { viewAny } = (usePage().props.auth as AuthProp).policies.notices;
|
||||
const model = data.data as DefaultModel;
|
||||
const actionModels = [
|
||||
Views.VISUALIZATION,
|
||||
Views.DUPLICATION,
|
||||
Views.ARCHIVATION,
|
||||
];
|
||||
|
||||
// Translations.
|
||||
const transBackToList = useTrans("uwil-backend::back.crud_back_to_list");
|
||||
const transTitle = useTrans("uwil-backend::crud.helpers.edition_model", {
|
||||
params: {
|
||||
model: useTrans(`models.classes.${modelName}`, {
|
||||
choice: 1,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
const beforeNode = () => {
|
||||
return (
|
||||
viewAny && (
|
||||
<div className="flex justify-end items-center gap-x-1.5">
|
||||
<BtnOptionnalLinkWithTooltip
|
||||
link={route(`back.${modelName}.index`)}
|
||||
content={<ArrowLeftIcon />}
|
||||
tooltipContent={<p>{transBackToList}</p>}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthenticatedLayout title={transTitle} beforeNode={beforeNode()}>
|
||||
<ModelActions
|
||||
actions={actionModels}
|
||||
modelRoute={modelName}
|
||||
model={model}
|
||||
modelUpdatedAt={model.updated_at.toString()}
|
||||
/>
|
||||
<Form
|
||||
model={model}
|
||||
modelName={modelName}
|
||||
modelRoute={modelName}
|
||||
action={Actions.UPDATE}
|
||||
method={Methods.PATCH}
|
||||
/>
|
||||
</AuthenticatedLayout>
|
||||
);
|
||||
}
|
||||
131
resources/ts/back/Pages/Notices/Form.tsx
Normal file
131
resources/ts/back/Pages/Notices/Form.tsx
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
// * Packages
|
||||
import { useForm, usePage } from "@inertiajs/react";
|
||||
import { BookTextIcon } from "lucide-react";
|
||||
// * Components shadcn
|
||||
//
|
||||
// * Other
|
||||
import FieldsetWrapper from "@/back/Components/FieldsetWrapper";
|
||||
import useTrans from "@/hooks/use-translations";
|
||||
import InputError from "@/back/Components/InputError";
|
||||
import SubmitButton from "@/back/Components/SubmitButton";
|
||||
import useCrudSubmit from "@/back/hooks/use-crud-submit";
|
||||
import { NoticeModel } from "@/back/types/project";
|
||||
import SimpleString from "@/back/Components/Inputs/SimpleString";
|
||||
import str from "@/hooks/use-string";
|
||||
import { AppEnv, LaravelEnum } from "@/types/global";
|
||||
import SimpleWysiwyg from "@/back/Components/Inputs/Wysiwyg/SimpleWysiwyg";
|
||||
import EnumSelect from "@/back/Components/Inputs/EnumSelect";
|
||||
|
||||
export default function Form({
|
||||
model,
|
||||
modelName,
|
||||
modelRoute,
|
||||
action,
|
||||
method,
|
||||
}: {
|
||||
model?: NoticeModel;
|
||||
modelName: string;
|
||||
modelRoute: string;
|
||||
action: string;
|
||||
method: number;
|
||||
}) {
|
||||
const { appEnv, noticeTypeEnum } = usePage().props as unknown as {
|
||||
appEnv: AppEnv;
|
||||
noticeTypeEnum: LaravelEnum;
|
||||
};
|
||||
|
||||
/** Form section */
|
||||
const { post, setData, data } = useForm({
|
||||
title: model?.title ? model?.title[appEnv.locale] : "",
|
||||
message: model?.message ? model?.message[appEnv.locale] : "",
|
||||
type: model?.type ?? noticeTypeEnum.primary,
|
||||
});
|
||||
|
||||
const submit = useCrudSubmit({
|
||||
method,
|
||||
modelRoute,
|
||||
modelName,
|
||||
action,
|
||||
model,
|
||||
post,
|
||||
data,
|
||||
modelNameTrans: useTrans(`models.classes.${modelName}`, {
|
||||
choice: 1,
|
||||
}),
|
||||
});
|
||||
|
||||
return (
|
||||
<form id="config-form" onSubmit={submit}>
|
||||
<InputError all />
|
||||
<FieldsetWrapper
|
||||
title={useTrans("uwil-backend::back.form_title_global")}
|
||||
icon={<BookTextIcon className="mr-1" />}
|
||||
description={useTrans("uwil-backend::back.form_subtitle_global")}
|
||||
className="sm:grid-cols-2 gap-4"
|
||||
>
|
||||
<SimpleString
|
||||
name="title"
|
||||
label={str(
|
||||
useTrans("uwil-backend::back.form_associate_to", {
|
||||
params: {
|
||||
attribute: useTrans(
|
||||
"validation.custom.title",
|
||||
),
|
||||
},
|
||||
choice: 1,
|
||||
}),
|
||||
)
|
||||
.ucFirst()
|
||||
.value()}
|
||||
placeholder="ex: John Doe"
|
||||
value={data.title}
|
||||
setData={setData}
|
||||
/>
|
||||
<EnumSelect
|
||||
name="type"
|
||||
options={noticeTypeEnum}
|
||||
label={str(
|
||||
useTrans("uwil-backend::back.form_associate_to", {
|
||||
params: {
|
||||
attribute: useTrans(
|
||||
"validation.custom.type",
|
||||
),
|
||||
},
|
||||
choice: 1,
|
||||
}),
|
||||
)
|
||||
.ucFirst()
|
||||
.value()}
|
||||
placeholder={`ex: ${noticeTypeEnum.primary.label}`}
|
||||
selected={data.type}
|
||||
setData={setData}
|
||||
/>
|
||||
<SimpleWysiwyg
|
||||
name="message"
|
||||
tableName="notices"
|
||||
label={str(
|
||||
useTrans("uwil-backend::back.form_associate_to", {
|
||||
params: {
|
||||
attribute: useTrans(
|
||||
"validation.custom.message",
|
||||
),
|
||||
},
|
||||
choice: 1,
|
||||
}),
|
||||
)
|
||||
.ucFirst()
|
||||
.value()}
|
||||
value={data.message}
|
||||
setData={setData}
|
||||
/>
|
||||
</FieldsetWrapper>
|
||||
<div className="text-center">
|
||||
<SubmitButton
|
||||
modelTrans={useTrans(`models.classes.${modelName}`, {
|
||||
choice: 1,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
207
resources/ts/back/Pages/Notices/Index.tsx
Normal file
207
resources/ts/back/Pages/Notices/Index.tsx
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
// * Packages
|
||||
import { route } from "ziggy-js";
|
||||
import { usePage } from "@inertiajs/react";
|
||||
import { Row } from "@tanstack/react-table";
|
||||
import { ArchiveIcon, ArrowLeftIcon, PlusIcon } from "lucide-react";
|
||||
// * Components shadcn
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
// * Other
|
||||
import AuthenticatedLayout from "@/back/Layouts/AuthenticatedLayout";
|
||||
import DataTable from "@/back/Components/Table/DataTable";
|
||||
import DataTableSearch from "@/back/Components/Table/DataTableSearch";
|
||||
import DataTableFilterButton from "@/back/Components/Table/DataTableFilterButton";
|
||||
import SoftdeleteWarning from "@/back/Components/SoftdeleteWarning";
|
||||
import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip";
|
||||
import { GetFormatedDate, GetTranslation } from "@/lib/utils";
|
||||
import { Views } from "@/back/lib/actions";
|
||||
import { AuthProp, DefaultModel, Resource } from "@/back/types/global";
|
||||
import str from "@/hooks/use-string";
|
||||
import useTrans from "@/hooks/use-translations";
|
||||
import RenderTranslationCell from "@/back/Components/Table/RenderTranslationCell";
|
||||
import { NoticeModel } from "@/back/types/project";
|
||||
import { LaravelEnum } from "@/types/global";
|
||||
|
||||
const getColumns = (noticeTypeEnum: LaravelEnum) => {
|
||||
return [
|
||||
{
|
||||
accessorKey: "title",
|
||||
header: ({ column }: { column: { id: string } }) => (
|
||||
<DataTableFilterButton
|
||||
label={str(
|
||||
GetTranslation(`validation.custom.${column.id}`),
|
||||
)
|
||||
.ucFirst()
|
||||
.value()}
|
||||
field={column.id}
|
||||
/>
|
||||
),
|
||||
cell: ({ row }: { row: Row<NoticeModel> }) => (
|
||||
<RenderTranslationCell value={row.getValue("title")} />
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "message",
|
||||
header: ({ column }: { column: { id: string } }) => (
|
||||
<DataTableFilterButton
|
||||
label={str(
|
||||
GetTranslation(`validation.custom.${column.id}`),
|
||||
)
|
||||
.ucFirst()
|
||||
.value()}
|
||||
field={column.id}
|
||||
/>
|
||||
),
|
||||
cell: ({ row }: { row: Row<NoticeModel> }) => (
|
||||
<RenderTranslationCell value={row.getValue("message")} renderHtml />
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "type",
|
||||
header: ({ column }: { column: { id: string } }) => (
|
||||
<DataTableFilterButton
|
||||
label={str(
|
||||
GetTranslation(`validation.custom.${column.id}`),
|
||||
)
|
||||
.ucFirst()
|
||||
.value()}
|
||||
field={column.id}
|
||||
/>
|
||||
),
|
||||
cell: ({ row }: { row: Row<NoticeModel> }) => (
|
||||
<Badge variant="outline">
|
||||
{str(
|
||||
String(
|
||||
Object.values(noticeTypeEnum).find(
|
||||
(type) => type.value === row.getValue("type"),
|
||||
)?.label,
|
||||
),
|
||||
)
|
||||
.ucFirst()
|
||||
.value()}
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "updated_at",
|
||||
header: ({ column }: { column: { id: string } }) => (
|
||||
<DataTableFilterButton
|
||||
label={str(
|
||||
GetTranslation(`uwil-backend::validation.attributes.${column.id}`),
|
||||
)
|
||||
.ucFirst()
|
||||
.value()}
|
||||
field={column.id}
|
||||
/>
|
||||
),
|
||||
cell: ({ row }: { row: Row<DefaultModel> }) => (
|
||||
<Badge variant="secondary">
|
||||
{GetFormatedDate(row.getValue("updated_at"))}
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export default function Index({
|
||||
models,
|
||||
searchFields,
|
||||
modelName,
|
||||
}: {
|
||||
models: Resource;
|
||||
searchFields: string;
|
||||
modelName: string;
|
||||
}) {
|
||||
const { auth, noticeTypeEnum } = usePage().props as unknown as {
|
||||
auth: AuthProp;
|
||||
noticeTypeEnum: LaravelEnum;
|
||||
};
|
||||
const { viewAny, viewTrash, create } = auth.policies.notices;
|
||||
const isArchiveRoute = route().current(`back.${modelName}.trash`);
|
||||
const actionModels = isArchiveRoute
|
||||
? [Views.RESTORATION, Views.FORCEDELETION]
|
||||
: [
|
||||
Views.VISUALIZATION,
|
||||
Views.EDITION,
|
||||
Views.DUPLICATION,
|
||||
Views.ARCHIVATION,
|
||||
];
|
||||
const columns = getColumns(noticeTypeEnum);
|
||||
|
||||
// Translations.
|
||||
const transModel = useTrans(`models.classes.${modelName}`, { choice: 2 });
|
||||
const transArchiveTitle = useTrans(
|
||||
"uwil-backend::crud.helpers.archives_model",
|
||||
{
|
||||
params: { model: transModel },
|
||||
},
|
||||
);
|
||||
const transTitle = str(isArchiveRoute ? transArchiveTitle : transModel)
|
||||
.ucFirst()
|
||||
.value();
|
||||
const transCrudBackList = useTrans("uwil-backend::back.crud_back_to_list");
|
||||
|
||||
const beforeNode = () => {
|
||||
return (
|
||||
isArchiveRoute &&
|
||||
viewAny && (
|
||||
<div className="flex justify-end items-center gap-x-1.5">
|
||||
<BtnOptionnalLinkWithTooltip
|
||||
link={route(`back.${modelName}.index`)}
|
||||
content={<ArrowLeftIcon />}
|
||||
tooltipContent={<p>{transCrudBackList}</p>}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
};
|
||||
const transCrudAddEntry = useTrans("uwil-backend::back.crud_add_entry");
|
||||
const transAccessTo = useTrans("uwil-backend::back.crud_access_to", {
|
||||
params: {
|
||||
target: useTrans("uwil-backend::back.archived_entries"),
|
||||
},
|
||||
});
|
||||
const afterNode = () => {
|
||||
return (
|
||||
!isArchiveRoute && (
|
||||
<div className="flex justify-end items-center gap-x-1.5">
|
||||
{viewTrash && (
|
||||
<BtnOptionnalLinkWithTooltip
|
||||
link={route(`back.${modelName}.trash`)}
|
||||
content={<ArchiveIcon />}
|
||||
tooltipContent={<p>{transAccessTo}</p>}
|
||||
variant="outline"
|
||||
/>
|
||||
)}
|
||||
{create && (
|
||||
<BtnOptionnalLinkWithTooltip
|
||||
link={route(`back.${modelName}.create`)}
|
||||
content={<PlusIcon />}
|
||||
tooltipContent={<p>{transCrudAddEntry}</p>}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthenticatedLayout
|
||||
title={transTitle}
|
||||
beforeNode={beforeNode()}
|
||||
afterNode={afterNode()}
|
||||
>
|
||||
{isArchiveRoute && <SoftdeleteWarning />}
|
||||
<DataTableSearch
|
||||
modelRoute={modelName}
|
||||
searchFields={searchFields}
|
||||
isArchiveRoute={isArchiveRoute}
|
||||
/>
|
||||
<DataTable
|
||||
columns={columns}
|
||||
modelRoute={modelName}
|
||||
modelsPaginate={models}
|
||||
actions={actionModels}
|
||||
/>
|
||||
</AuthenticatedLayout>
|
||||
);
|
||||
}
|
||||
154
resources/ts/back/Pages/Notices/Show.tsx
Normal file
154
resources/ts/back/Pages/Notices/Show.tsx
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
// * Packages
|
||||
import { usePage } from "@inertiajs/react";
|
||||
import { route } from "ziggy-js";
|
||||
import { ArrowLeftIcon } from "lucide-react";
|
||||
// * Components shadcn
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
// * Other
|
||||
import AuthenticatedLayout from "@/back/Layouts/AuthenticatedLayout";
|
||||
import DetailsTable from "@/back/Components/Table/DetailsTable";
|
||||
import ModelActions from "@/back/Components/ModelActions";
|
||||
import BtnOptionnalLinkWithTooltip from "@/back/Components/BtnOptionnalLinkWithTooltip";
|
||||
import { GetFormatedDate, GetTranslation } from "@/lib/utils";
|
||||
import { Views } from "@/back/lib/actions";
|
||||
import { AuthProp, DefaultModel, SingleResource } from "@/back/types/global";
|
||||
import str from "@/hooks/use-string";
|
||||
import useTrans from "@/hooks/use-translations";
|
||||
import { LaravelEnum } from "@/types/global";
|
||||
import { TranslationField } from "@/back/types/i18n";
|
||||
import RenderTranslationCell from "@/back/Components/Table/RenderTranslationCell";
|
||||
|
||||
const getColumns = (noticeTypeEnum: LaravelEnum) => {
|
||||
return [
|
||||
{
|
||||
accessorKey: "title",
|
||||
header: str(
|
||||
GetTranslation("validation.custom.title"),
|
||||
)
|
||||
.ucFirst()
|
||||
.value(),
|
||||
cell: ({ value }: { value: TranslationField }) => (
|
||||
<RenderTranslationCell value={value} />
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "message",
|
||||
header: str(
|
||||
GetTranslation("validation.custom.message"),
|
||||
)
|
||||
.ucFirst()
|
||||
.value(),
|
||||
cell: ({ value }: { value: TranslationField }) => (
|
||||
<RenderTranslationCell value={value} />
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "type",
|
||||
header: str(GetTranslation("validation.custom.type"))
|
||||
.ucFirst()
|
||||
.value(),
|
||||
cell: ({ value }: { value: number }) => (
|
||||
<Badge variant="outline">
|
||||
{str(
|
||||
String(
|
||||
Object.values(noticeTypeEnum).find((role) => role.value === value)
|
||||
?.label,
|
||||
),
|
||||
)
|
||||
.ucFirst()
|
||||
.value()}
|
||||
</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "created_at",
|
||||
header: str(
|
||||
GetTranslation("uwil-backend::validation.attributes.created_at"),
|
||||
)
|
||||
.ucFirst()
|
||||
.value(),
|
||||
cell: ({ value }: { value: string }) => (
|
||||
<Badge variant="secondary">{GetFormatedDate(value)}</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "updated_at",
|
||||
header: str(
|
||||
GetTranslation("uwil-backend::validation.attributes.updated_at"),
|
||||
)
|
||||
.ucFirst()
|
||||
.value(),
|
||||
cell: ({ value }: { value: string }) => (
|
||||
<Badge variant="secondary">{GetFormatedDate(value)}</Badge>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: "deleted_at",
|
||||
header: str(
|
||||
GetTranslation("uwil-backend::validation.attributes.deleted_at"),
|
||||
)
|
||||
.ucFirst()
|
||||
.value(),
|
||||
cell: ({ value }: { value: string }) => (
|
||||
<Badge variant="secondary">{GetFormatedDate(value)}</Badge>
|
||||
),
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export default function Show({
|
||||
data,
|
||||
modelName,
|
||||
}: {
|
||||
data: SingleResource;
|
||||
modelName: string;
|
||||
}) {
|
||||
const { auth, noticeTypeEnum } = usePage().props as unknown as {
|
||||
auth: AuthProp;
|
||||
noticeTypeEnum: LaravelEnum;
|
||||
};
|
||||
const { viewAny } = auth.policies.notices;
|
||||
const model = data.data as DefaultModel;
|
||||
const actionModels = [Views.EDITION, Views.DUPLICATION, Views.ARCHIVATION];
|
||||
const transTitle = useTrans(
|
||||
"uwil-backend::crud.helpers.visualization_model",
|
||||
{
|
||||
params: {
|
||||
model: useTrans(`models.classes.${modelName}`, {
|
||||
choice: 1,
|
||||
}),
|
||||
},
|
||||
},
|
||||
);
|
||||
const columns = getColumns(noticeTypeEnum);
|
||||
|
||||
// Translations.
|
||||
const transCrudBackToList = useTrans("uwil-backend::back.crud_back_to_list");
|
||||
|
||||
const beforeNode = () => {
|
||||
return (
|
||||
viewAny && (
|
||||
<div className="flex justify-end items-center gap-x-1.5">
|
||||
<BtnOptionnalLinkWithTooltip
|
||||
link={route(`back.${modelName}.index`)}
|
||||
content={<ArrowLeftIcon />}
|
||||
tooltipContent={<p>{transCrudBackToList}</p>}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthenticatedLayout title={transTitle} beforeNode={beforeNode()}>
|
||||
<ModelActions
|
||||
actions={actionModels}
|
||||
modelRoute={modelName}
|
||||
model={model}
|
||||
modelUpdatedAt={model.updated_at.toString()}
|
||||
/>
|
||||
{/* @ts-expect-error Weird error type on cell. */}
|
||||
<DetailsTable columns={columns} model={model} />
|
||||
</AuthenticatedLayout>
|
||||
);
|
||||
}
|
||||
8
resources/ts/back/types/project.d.ts
vendored
Normal file
8
resources/ts/back/types/project.d.ts
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import { LaravelEnum } from "@/types/global";
|
||||
import { DefaultModel } from "@/back/types/global";
|
||||
|
||||
type NoticeModel = DefaultModel & {
|
||||
title: TranslationField;
|
||||
message: TranslationField;
|
||||
type: LaravelEnum;
|
||||
};
|
||||
14
routes/back/notices.php
Normal file
14
routes/back/notices.php
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
use App\Http\Controllers\Back\NoticeController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::get('/notices/trash', [NoticeController::class, 'index'])
|
||||
->name('notices.trash');
|
||||
Route::patch('/notices/{notice}/restore', [NoticeController::class, 'restore'])
|
||||
->name('notices.restore');
|
||||
Route::delete('/notices/{notice}/force', [NoticeController::class, 'forceDestroy'])
|
||||
->name('notices.forceDestroy');
|
||||
Route::resource('notices', NoticeController::class);
|
||||
Route::get('/notices/{notice}/duplicate', [NoticeController::class, 'duplicate'])
|
||||
->name('notices.duplicate');
|
||||
39
routes/breadcrumbs/notices.php
Normal file
39
routes/breadcrumbs/notices.php
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
use Diglactic\Breadcrumbs\Breadcrumbs;
|
||||
use Diglactic\Breadcrumbs\Generator;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
Breadcrumbs::for('back.notices.index', function (Generator $trail) {
|
||||
$trail->push(
|
||||
Str::ucfirst(trans_choice('models.classes.notices', \INF)),
|
||||
route('back.notices.index')
|
||||
);
|
||||
});
|
||||
Breadcrumbs::for('back.notices.create', function (Generator $trail) {
|
||||
$trail->parent('back.notices.index');
|
||||
$trail->push(
|
||||
Str::ucfirst(trans_choice('models.classes.notices', 1)),
|
||||
route('back.notices.create')
|
||||
);
|
||||
});
|
||||
Breadcrumbs::for('back.notices.edit', function (Generator $trail) {
|
||||
$trail->parent('back.notices.index');
|
||||
$trail->push(Str::ucfirst(trans('uwil-backend::crud.actions.edit')), route('back.notices.edit', [
|
||||
'notice' => request()->notice
|
||||
]));
|
||||
});
|
||||
Breadcrumbs::for('back.notices.duplicate', function (Generator $trail) {
|
||||
$trail->parent('back.notices.index');
|
||||
$trail->push(Str::ucfirst(trans('uwil-backend::crud.actions.duplicate')));
|
||||
});
|
||||
Breadcrumbs::for('back.notices.show', function (Generator $trail) {
|
||||
$trail->parent('back.notices.index');
|
||||
$trail->push(Str::ucfirst(trans('uwil-backend::crud.actions.visualize')), route('back.notices.show', [
|
||||
'notice' => request()->notice
|
||||
]));
|
||||
});
|
||||
Breadcrumbs::for('back.notices.trash', function (Generator $trail) {
|
||||
$trail->parent('back.notices.index');
|
||||
$trail->push(Str::ucfirst(trans('uwil-backend::back.archives')));
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue