Переглянути джерело

feat: :sparkles: feat (notificacoes adm) criada pagina de notificacoes adm

foi criada a pagina de notificacoes no layout adm, incluindo a tab de cadastrar nova notificacao definidio destinatarios, e a tela de visualizar todas as notificacoes

fase:dev | origin:escopo
Gustavo Zanatta 3 тижнів тому
батько
коміт
597531916d

+ 14 - 0
app/Http/Controllers/NotificationController.php

@@ -7,6 +7,7 @@ use App\Http\Resources\NotificationResource;
 use App\Http\Resources\NotificationSendResource;
 use App\Services\NotificationService;
 use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Auth;
 
 class NotificationController extends Controller
@@ -19,6 +20,19 @@ class NotificationController extends Controller
         return $this->successResponse(payload: NotificationResource::collection($items));
     }
 
+    public function indexPaginated(Request $request): JsonResponse
+    {
+        $perPage   = min((int) $request->get('per_page', 12), 100);
+        $paginator = $this->service->getAllPaginated($perPage);
+
+        return $this->successResponse(payload: [
+            'data'  => NotificationResource::collection($paginator->items()),
+            'total' => $paginator->total(),
+            'from'  => $paginator->firstItem() ?? 0,
+            'to'    => $paginator->lastItem() ?? 0,
+        ]);
+    }
+
     public function store(NotificationRequest $request): JsonResponse
     {
         $item = $this->service->create($request->validated());

+ 6 - 9
app/Http/Requests/NotificationRequest.php

@@ -11,18 +11,15 @@ class NotificationRequest extends FormRequest
     public function rules(): array
     {
         $rules = [
-            'type'               => 'sometimes|string|max:50',
-            'source'             => 'sometimes|nullable|string|max:100',
-            'source_id'          => 'sometimes|nullable|integer',
-            'title'              => 'sometimes|string|max:255',
-            'message'            => 'sometimes|string',
-            'recipient'          => ['sometimes', Rule::enum(NotificationRecipientEnum::class)],
-            'recipient_position' => 'sometimes|nullable|string|max:100',
-            'recipient_sector'   => 'sometimes|nullable|string|max:100',
+            'title'                  => 'sometimes|string|max:255',
+            'message'                => 'sometimes|string',
+            'recipient'              => ['sometimes', Rule::enum(NotificationRecipientEnum::class)],
+            'recipient_position_id'  => 'sometimes|nullable|integer|exists:positions,id',
+            'recipient_sector_id'    => 'sometimes|nullable|integer|exists:sectors,id',
+            'image'                  => 'sometimes|nullable|file|mimes:jpg,jpeg,png,webp|max:5120',
         ];
 
         if ($this->isMethod('post')) {
-            $rules['type']      = 'required|string|max:50';
             $rules['title']     = 'required|string|max:255';
             $rules['message']   = 'required|string';
             $rules['recipient'] = ['required', Rule::enum(NotificationRecipientEnum::class)];

+ 14 - 13
app/Http/Resources/NotificationResource.php

@@ -11,19 +11,20 @@ class NotificationResource extends JsonResource
     public function toArray(Request $request): array
     {
         return [
-            'id'                 => $this->id,
-            'type'               => $this->type,
-            'source'             => $this->source,
-            'source_id'          => $this->source_id,
-            'title'              => $this->title,
-            'message'            => $this->message,
-            'recipient'          => $this->recipient,
-            'recipient_position' => $this->recipient_position,
-            'recipient_sector'   => $this->recipient_sector,
-            'created_by'         => $this->created_by,
-            'created_by_user'    => $this->whenLoaded('createdBy', fn() => new UserResource($this->createdBy)),
-            'created_at'         => Carbon::parse($this->created_at)->format('Y-m-d H:i:s'),
-            'updated_at'         => Carbon::parse($this->updated_at)->format('Y-m-d H:i:s'),
+            'id'                    => $this->id,
+            'type'                  => $this->type,
+            'title'                 => $this->title,
+            'message'               => $this->message,
+            'recipient'             => $this->recipient,
+            'recipient_position_id' => $this->recipient_position_id,
+            'recipient_sector_id'   => $this->recipient_sector_id,
+            'sent_count'            => $this->when($this->sends_count !== null, $this->sends_count),
+            'seen_count'            => $this->when($this->seen_count !== null, $this->seen_count),
+            'image_url'             => $this->whenLoaded('media', fn() => $this->media->first()?->url),
+            'created_by'            => $this->created_by,
+            'created_by_user'       => $this->whenLoaded('createdBy', fn() => new UserResource($this->createdBy)),
+            'created_at'            => Carbon::parse($this->created_at)->format('Y-m-d H:i:s'),
+            'updated_at'            => Carbon::parse($this->updated_at)->format('Y-m-d H:i:s'),
         ];
     }
 }

+ 8 - 1
app/Models/Notification.php

@@ -19,7 +19,9 @@ class Notification extends Model
     protected function casts(): array
     {
         return [
-            'recipient' => NotificationRecipientEnum::class,
+            'recipient'             => NotificationRecipientEnum::class,
+            'recipient_position_id' => 'integer',
+            'recipient_sector_id'   => 'integer',
         ];
     }
 
@@ -32,4 +34,9 @@ class Notification extends Model
     {
         return $this->hasMany(NotificationSend::class);
     }
+
+    public function media(): HasMany
+    {
+        return $this->hasMany(Media::class, 'source_id')->where('source', 'notification');
+    }
 }

+ 1 - 1
app/Services/MediaService.php

@@ -39,7 +39,7 @@ class MediaService
             'source_id' => $sourceId,
             'path'      => $path,
             'name'      => $file->getClientOriginalName(),
-            'url'       => Storage::url($path),
+            'url'       => Storage::disk('public')->url($path),
         ]);
     }
 

+ 33 - 6
app/Services/NotificationService.php

@@ -7,31 +7,58 @@ use App\Enums\UserTypeEnum;
 use App\Models\Notification;
 use App\Models\NotificationSend;
 use App\Models\User;
+use Illuminate\Contracts\Pagination\LengthAwarePaginator;
 use Illuminate\Database\Eloquent\Collection;
+use Illuminate\Http\UploadedFile;
 use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\DB;
 
 class NotificationService
 {
+    public function __construct(protected MediaService $mediaService) {}
+
     public function getAll(): Collection
     {
         return Notification::with('createdBy')->orderBy('created_at', 'desc')->get();
     }
 
+    public function getAllPaginated(int $perPage = 12): LengthAwarePaginator
+    {
+        return Notification::with(['createdBy', 'media'])
+            ->withCount([
+                'sends',
+                'sends as seen_count' => fn($q) => $q->where('read', true),
+            ])
+            ->orderBy('created_at', 'desc')
+            ->paginate($perPage);
+    }
+
     public function findById(int $id): ?Notification
     {
-        return Notification::with(['createdBy', 'sends'])->find($id);
+        return Notification::with(['createdBy', 'sends', 'media'])->find($id);
     }
 
     public function create(array $data): Notification
     {
         return DB::transaction(function () use ($data) {
+            $image = null;
+            if (isset($data['image']) && $data['image'] instanceof UploadedFile) {
+                $image = $data['image'];
+            }
+            unset($data['image']);
+
             $data['created_by'] = Auth::id();
+            $data['type']       = $data['type'] ?? 'manual';
+
             $notification = Notification::create($data);
 
+            if ($image) {
+                $this->mediaService->upload($image, 'notification', $notification->id, 'imagem');
+            }
+
             $this->dispatchSends($notification);
 
-            return $notification;
+            return $notification->load('media');
         });
     }
 
@@ -79,12 +106,12 @@ class NotificationService
             default                              => null,
         };
 
-        if ($notification->recipient_position) {
-            $query->whereHas('position', fn($q) => $q->where('name', $notification->recipient_position));
+        if ($notification->recipient_position_id) {
+            $query->where('position_id', $notification->recipient_position_id);
         }
 
-        if ($notification->recipient_sector) {
-            $query->whereHas('sector', fn($q) => $q->where('name', $notification->recipient_sector));
+        if ($notification->recipient_sector_id) {
+            $query->where('sector_id', $notification->recipient_sector_id);
         }
 
         $users = $query->pluck('id');

+ 42 - 0
database/migrations/2026_05_15_100000_update_custom_notifications_recipient_columns.php

@@ -0,0 +1,42 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    public function up(): void
+    {
+        Schema::table('custom_notifications', function (Blueprint $table) {
+            $table->dropColumn(['recipient_position', 'recipient_sector']);
+        });
+
+        Schema::table('custom_notifications', function (Blueprint $table) {
+            $table->unsignedBigInteger('recipient_position_id')->nullable()->after('recipient');
+            $table->unsignedBigInteger('recipient_sector_id')->nullable()->after('recipient_position_id');
+
+            $table->foreign('recipient_position_id')->references('id')->on('positions')->nullOnDelete();
+            $table->foreign('recipient_sector_id')->references('id')->on('sectors')->nullOnDelete();
+        });
+
+        Schema::table('custom_notifications', function (Blueprint $table) {
+            $table->string('type')->default('manual')->change();
+        });
+    }
+
+    public function down(): void
+    {
+        Schema::table('custom_notifications', function (Blueprint $table) {
+            $table->dropForeign(['recipient_position_id']);
+            $table->dropForeign(['recipient_sector_id']);
+            $table->dropColumn(['recipient_position_id', 'recipient_sector_id']);
+        });
+
+        Schema::table('custom_notifications', function (Blueprint $table) {
+            $table->string('recipient_position')->nullable()->after('recipient');
+            $table->string('recipient_sector')->nullable()->after('recipient_position');
+            $table->string('type')->default(null)->change();
+        });
+    }
+};

+ 2 - 0
routes/authRoutes/notification.php

@@ -6,6 +6,8 @@ use Illuminate\Support\Facades\Route;
 Route::controller(NotificationController::class)->prefix('notification')->group(function () {
     Route::get('/', 'index')->middleware('permission:notificacao,view');
 
+    Route::get('/paginated', 'indexPaginated')->middleware('permission:notificacao,view');
+
     Route::post('/', 'store')->middleware('permission:notificacao,add');
 
     Route::get('/my/unread', 'myUnread')->middleware('permission:notificacao,view');