feat: add notification center with bell dropdown, full page, and workspace scoping (Story 3.3)
Enhance NotificationDropdown with type-specific icons, French description builder, click-to-navigate with mark-as-read, and "Voir toutes les notifications" link. Add full notifications page at /notifications with pagination (25/page), individual mark-as-read, and empty state. Includes code review fixes: workspace-scoped unread count and dropdown items, race condition fix (mark-as-read before navigate), efficient markAllAsRead via direct update, deleted declaration URL handling, and per-workspace cache keys. 7 new feature tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use App\Models\Declaration;
|
||||
use App\Models\User;
|
||||
use App\Models\WorkspaceUser;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
@@ -81,26 +83,52 @@ class HandleInertiaRequests extends Middleware
|
||||
],
|
||||
'sidebarOpen' => ! $request->hasCookie('sidebar_state') || $request->cookie('sidebar_state') === 'true',
|
||||
'userNotifications' => [
|
||||
'unread_count' => $user ? Cache::remember(
|
||||
"user:{$user->id}:unread_notifications",
|
||||
'unread_count' => $user && $currentWorkspace ? Cache::remember(
|
||||
"user:{$user->id}:workspace:{$currentWorkspace['id']}:unread_notifications",
|
||||
60,
|
||||
fn () => $user->unreadNotifications()->count()
|
||||
fn () => $user->unreadNotifications()
|
||||
->whereJsonContains('data->workspace_id', $currentWorkspace['id'])
|
||||
->count()
|
||||
) : 0,
|
||||
'readUrl' => fn () => $user ? route('notifications.read', ['id' => '__ID__']) : null,
|
||||
'readAllUrl' => fn () => $user ? route('notifications.readAll') : null,
|
||||
'items' => Inertia::defer(function () use ($user) {
|
||||
if (! $user) {
|
||||
'notificationsUrl' => fn () => $user ? route('notifications.index') : null,
|
||||
'items' => Inertia::defer(function () use ($user, $currentWorkspace) {
|
||||
if (! $user || ! $currentWorkspace) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
return $user->notifications()->latest()->take(10)->get()->map(fn ($n) => [
|
||||
'id' => $n->id,
|
||||
'type' => class_basename($n->type),
|
||||
'data' => $n->data,
|
||||
'read_at' => $n->read_at?->toISOString(),
|
||||
'created_at' => $n->created_at->diffForHumans(),
|
||||
])->all();
|
||||
$notifications = $user->notifications()
|
||||
->whereJsonContains('data->workspace_id', $currentWorkspace['id'])
|
||||
->latest()
|
||||
->take(10)
|
||||
->get();
|
||||
|
||||
$declarationIds = $notifications->pluck('data.declaration_id')->filter()->unique()->values();
|
||||
$senderIds = $notifications->pluck('data.sender_id')->filter()->unique()->values();
|
||||
|
||||
$declarations = Declaration::whereIn('id', $declarationIds)->pluck('title', 'id');
|
||||
$senders = User::whereIn('id', $senderIds)->pluck('name', 'id');
|
||||
|
||||
return $notifications->map(function ($n) use ($declarations, $senders) {
|
||||
$data = $n->data;
|
||||
$declarationId = $data['declaration_id'] ?? null;
|
||||
$senderId = $data['sender_id'] ?? null;
|
||||
|
||||
$declarationExists = $declarationId && isset($declarations[$declarationId]);
|
||||
$data['declaration_title'] = $declarationExists ? $declarations[$declarationId] : null;
|
||||
$data['sender_name'] = $senderId ? ($senders[$senderId] ?? null) : null;
|
||||
$data['url'] = $declarationExists ? route('declarations.show', $declarationId) : null;
|
||||
|
||||
return [
|
||||
'id' => $n->id,
|
||||
'type' => class_basename($n->type),
|
||||
'data' => $data,
|
||||
'read_at' => $n->read_at?->toISOString(),
|
||||
'created_at' => $n->created_at->diffForHumans(),
|
||||
];
|
||||
})->all();
|
||||
} catch (\Throwable) {
|
||||
return [];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user