work in progress

This commit is contained in:
Daan Meijer 2026-06-23 13:54:27 +02:00
parent d751cd4fdd
commit 1e33bfb50b
10 changed files with 70 additions and 25 deletions

View File

@ -11,10 +11,10 @@ class DashboardController extends Controller
public function index(Request $request, ActivityService $activityService) public function index(Request $request, ActivityService $activityService)
{ {
$user = $request->user(); $user = $request->user();
$unreadDynamics = $activityService->getUnreadDynamicsGrouped($user); $unreadEntities = $activityService->getUnreadEntitiesGrouped($user);
return Inertia::render('Dashboard', [ return Inertia::render('Dashboard', [
'unreadDynamics' => $unreadDynamics, 'unreadEntities' => $unreadEntities,
]); ]);
} }
} }

View File

@ -85,9 +85,7 @@ class LedgerController extends Controller
return Inertia::render('Ledgers/Show', [ return Inertia::render('Ledgers/Show', [
'dynamic' => new DynamicResource($dynamic), 'dynamic' => new DynamicResource($dynamic),
'ledger' => new LedgerResource($ledger), 'ledger' => new LedgerResource($ledger),
'mutations' => MutationResource::collection($ledger->mutations), 'messages' => MessageResource::collection($dynamic->getOrCreateChat()->messages()->with(['user', 'media'])->latest()->paginate(\App\Models\Message::PAGINATION_COUNT)),
'participants' => UserResource::collection($dynamic->participants),
'messages' => MessageResource::collection($dynamic->chat->messages()->with(['user', 'media'])->latest()->paginate(\App\Models\Message::PAGINATION_COUNT)),
'can' => [ 'can' => [
'update' => $request->user()->can('update', $ledger), 'update' => $request->user()->can('update', $ledger),
'close' => $request->user()->can('close', $ledger), 'close' => $request->user()->can('close', $ledger),
@ -99,7 +97,7 @@ class LedgerController extends Controller
{ {
$this->authorize('view', $ledger); $this->authorize('view', $ledger);
return MessageResource::collection($dynamic->chat->messages()->with(['user', 'media'])->latest()->paginate(\App\Models\Message::PAGINATION_COUNT)); return MessageResource::collection($dynamic->getOrCreateChat()->messages()->with(['user', 'media'])->latest()->paginate(\App\Models\Message::PAGINATION_COUNT));
} }
/** /**

View File

@ -50,7 +50,7 @@ class HandleInertiaRequests extends Middleware
$service = app(ActivityService::class); $service = app(ActivityService::class);
return count($service->getUnreadDynamicsGrouped($request->user())); return count($service->getUnreadEntitiesGrouped($request->user()));
}, },
]; ];
} }

View File

@ -0,0 +1,24 @@
<?php
namespace App\Http\Resources;
use Illuminate\Http\Request;
class ChatResource extends BaseResource
{
/**
* Transform the resource into an array.
*
* @return array<string, mixed>
*/
public function toArray(Request $request): array
{
$data = parent::toArray($request);
if ($this->whenLoaded('messages')) {
$data['messages'] = MessageResource::collection($this->messages);
}
return $data;
}
}

View File

@ -17,9 +17,12 @@ class DynamicResource extends BaseResource
if ($this->ledgers) { if ($this->ledgers) {
$result['ledgers'] = LedgerResource::collection($this->ledgers); $result['ledgers'] = LedgerResource::collection($this->ledgers);
} }
if ($this->participants) { if ($this->whenLoaded('participants')) {
$result['participants'] = ParticipantResource::collection($this->participants); $result['participants'] = ParticipantResource::collection($this->participants);
} }
if ($this->whenLoaded('chat')) {
$result['chat'] = new ChatResource($this->chat);
}
return $result; return $result;
} }
} }

View File

@ -13,6 +13,10 @@ class LedgerResource extends BaseResource
*/ */
public function toArray(Request $request): array public function toArray(Request $request): array
{ {
return parent::toArray($request); $data = parent::toArray($request);
$data['mutations'] = MutationResource::collection($this->whenLoaded('mutations'));
return $data;
} }
} }

View File

@ -9,6 +9,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphOne; use Illuminate\Database\Eloquent\Relations\MorphOne;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use App\Models\Chat;
class Dynamic extends Model class Dynamic extends Model
{ {
@ -64,4 +65,13 @@ class Dynamic extends Model
public function getUrlAttribute(): string { public function getUrlAttribute(): string {
return route('dynamics.show', $this); return route('dynamics.show', $this);
} }
public function getOrCreateChat(): Chat
{
if ($this->chat) {
return $this->chat;
}
return $this->chat()->create([]);
}
} }

View File

@ -45,8 +45,9 @@ class ActivityService
return $cursor ? $cursor->read_at : Carbon::parse('1970-01-01'); return $cursor ? $cursor->read_at : Carbon::parse('1970-01-01');
} }
public function createMessage($chat, $user, $content, $subject = null) public function createMessage($dynamic, $user, $content, $subject = null)
{ {
$chat = $dynamic->getOrCreateChat();
$message = $chat->messages()->create([ $message = $chat->messages()->create([
'user_id' => $user ? $user->id : null, 'user_id' => $user ? $user->id : null,
'content' => $content, 'content' => $content,
@ -92,6 +93,11 @@ class ActivityService
*/ */
public function getActivitiesForDynamic(Dynamic $dynamic): array public function getActivitiesForDynamic(Dynamic $dynamic): array
{ {
$chat = $dynamic->getOrCreateChat();
if (!$chat) {
return [];
}
$participants = $dynamic->participants()->withPivot('display_name')->get(); $participants = $dynamic->participants()->withPivot('display_name')->get();
$participantsMap = $participants->reduce(function ($acc, $p) { $participantsMap = $participants->reduce(function ($acc, $p) {
$acc[$p->id] = $p->pivot->display_name ?? $p->name; $acc[$p->id] = $p->pivot->display_name ?? $p->name;
@ -99,7 +105,7 @@ class ActivityService
return $acc; return $acc;
}, []); }, []);
$messages = Message::where('chat_id', $dynamic->chat->id) $messages = Message::where('chat_id', $chat->id)
->with(['user', 'subject']) ->with(['user', 'subject'])
->latest() ->latest()
->get(); ->get();
@ -122,10 +128,10 @@ class ActivityService
/** /**
* Get unread activities grouped by active entities (Dynamics, Ledgers) for the given user. * Get unread activities grouped by active entities (Dynamics, Ledgers) for the given user.
*/ */
public function getUnreadDynamicsGrouped(User $user): array public function getUnreadEntitiesGrouped(User $user): array
{ {
$groupedDynamics = []; $groupedDynamics = [];
$participatingDynamics = $user->dynamics()->with('ledgers')->get(); $participatingDynamics = $user->dynamics()->with(['chat', 'ledgers'])->get();
foreach ($participatingDynamics as $dynamic) { foreach ($participatingDynamics as $dynamic) {
$readAt = $this->getCursorReadAt($user, $dynamic); $readAt = $this->getCursorReadAt($user, $dynamic);

View File

@ -14,7 +14,7 @@ defineOptions({
}); });
defineProps<{ defineProps<{
unreadDynamics: Array<{ unreadEntities: Array<{
id: number; id: number;
name: string; name: string;
url: string; url: string;
@ -50,10 +50,10 @@ function formatTime(isoString: string): string {
<div class="c-dashboard__container"> <div class="c-dashboard__container">
<h2 class="c-dashboard__title">Recent Activity</h2> <h2 class="c-dashboard__title">Recent Activity</h2>
<div v-if="unreadDynamics.length > 0" class="c-dashboard__grid"> <div v-if="unreadEntities.length > 0" class="c-dashboard__grid">
<div <div
v-for="dynamic in unreadDynamics" v-for="entity in unreadEntities"
:key="dynamic.id" :key="entity.id"
class="c-dashboard__card" class="c-dashboard__card"
> >
<div class="c-dashboard__card-header"> <div class="c-dashboard__card-header">

View File

@ -18,7 +18,7 @@ test('authenticated users can visit the dashboard', function () {
$response = $this->get(route('dashboard')); $response = $this->get(route('dashboard'));
$response->assertOk(); $response->assertOk();
$response->assertInertia(fn ($page) => $page->component('Dashboard')->has('unreadDynamics')); $response->assertInertia(fn ($page) => $page->component('Dashboard')->has('unreadEntities'));
}); });
test('visiting dynamic updates the read cursor', function () { test('visiting dynamic updates the read cursor', function () {
@ -100,12 +100,12 @@ test('dashboard groups and filters unread entities correctly based on cursor', f
// Verify unread grouping structure // Verify unread grouping structure
$response->assertInertia(fn ($page) => $page $response->assertInertia(fn ($page) => $page
->component('Dashboard') ->component('Dashboard')
->where('unreadDynamics.0.name', 'Testing Dynamic') ->where('unreadEntities.0.name', 'Testing Dynamic')
->where('unreadDynamics.0.unread_count', 1) ->where('unreadEntities.0.unread_count', 1)
->has('unreadDynamics.0.context_activities', 1) // Should have old message as context ->has('unreadEntities.0.context_activities', 1) // Should have old message as context
->where('unreadDynamics.0.context_activities.0.content', 'Old message context') ->where('unreadEntities.0.context_activities.0.content', 'Old message context')
->has('unreadDynamics.0.new_activities', 1) // Should have unread message ->has('unreadEntities.0.new_activities', 1) // Should have unread message
->where('unreadDynamics.0.new_activities.0.content', 'New unread message alert') ->where('unreadEntities.0.new_activities.0.content', 'New unread message alert')
); );
// Now visit the Dynamic, which clears the unread count // Now visit the Dynamic, which clears the unread count
@ -116,7 +116,7 @@ test('dashboard groups and filters unread entities correctly based on cursor', f
$response2->assertOk(); $response2->assertOk();
$response2->assertInertia(fn ($page) => $page $response2->assertInertia(fn ($page) => $page
->component('Dashboard') ->component('Dashboard')
->has('unreadDynamics', 0) ->has('unreadEntities', 0)
); );
Carbon::setTestNow(); // Reset test time Carbon::setTestNow(); // Reset test time