diff --git a/app/Http/Controllers/MutationController.php b/app/Http/Controllers/MutationController.php
index 8a9c824..bcc9fc8 100644
--- a/app/Http/Controllers/MutationController.php
+++ b/app/Http/Controllers/MutationController.php
@@ -67,6 +67,30 @@ class MutationController extends Controller
return $mutation;
});
+ // Log to Mutation and Dynamic chats
+ $user = $request->user();
+
+ $mutationMsg = $mutation->chat->messages()->create([
+ 'user_id' => $user->id,
+ 'content' => $status === 'approved'
+ ? "System: Entry was created by {$user->name}."
+ : "System: Suggestion was created by {$user->name}.",
+ ]);
+ broadcast(new \App\Events\MessageSent($mutationMsg));
+
+ if ($status === 'approved') {
+ $dynamicMsg = $dynamic->chat->messages()->create([
+ 'user_id' => $user->id,
+ 'content' => "System: {$user->name} added entry \"{$mutation->description}\" for " . ($mutation->amount >= 0 ? '+' : '') . "{$mutation->amount} points on \"{$ledger->name}\" ledger.",
+ ]);
+ } else {
+ $dynamicMsg = $dynamic->chat->messages()->create([
+ 'user_id' => $user->id,
+ 'content' => "System: {$user->name} suggested \"{$mutation->description}\" for " . ($mutation->amount >= 0 ? '+' : '') . "{$mutation->amount} points on \"{$ledger->name}\" ledger.",
+ ]);
+ }
+ broadcast(new \App\Events\MessageSent($dynamicMsg));
+
// Broadcast the real-time creation event!
broadcast(new \App\Events\MutationCreated($mutation));
diff --git a/resources/js/app.ts b/resources/js/app.ts
index b0c9c1f..3816a62 100644
--- a/resources/js/app.ts
+++ b/resources/js/app.ts
@@ -1,10 +1,10 @@
import { createInertiaApp } from '@inertiajs/vue3';
+import { configureEcho } from '@laravel/echo-vue';
import { initializeTheme } from '@/composables/useAppearance';
import AppLayout from '@/layouts/AppLayout.vue';
import AuthLayout from '@/layouts/AuthLayout.vue';
import SettingsLayout from '@/layouts/settings/Layout.vue';
import { initializeFlashToast } from '@/lib/flashToast';
-import { configureEcho } from '@laravel/echo-vue';
configureEcho({
broadcaster: 'reverb',
diff --git a/resources/js/components/AddMutationForm.vue b/resources/js/components/AddMutationForm.vue
new file mode 100644
index 0000000..f096c72
--- /dev/null
+++ b/resources/js/components/AddMutationForm.vue
@@ -0,0 +1,136 @@
+
+
+
+
+
diff --git a/resources/js/components/Chat.vue b/resources/js/components/Chat.vue
index 8fb1b16..28f298b 100644
--- a/resources/js/components/Chat.vue
+++ b/resources/js/components/Chat.vue
@@ -1,9 +1,9 @@
+
+
+
+
+
+
Create a New Ledger
+
+
+
+
+
+
diff --git a/resources/js/components/LedgerList.vue b/resources/js/components/LedgerList.vue
new file mode 100644
index 0000000..2c87ab8
--- /dev/null
+++ b/resources/js/components/LedgerList.vue
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+ Ledgers
+
+
+
+
+ No ledgers found for this dynamic.
+
+
+
diff --git a/resources/js/components/MutationList.vue b/resources/js/components/MutationList.vue
new file mode 100644
index 0000000..0a9b91a
--- /dev/null
+++ b/resources/js/components/MutationList.vue
@@ -0,0 +1,170 @@
+
+
+
+
+
+ Mutations
+
+
+
+ No mutations found for this ledger.
+
+
+
diff --git a/resources/js/components/ParticipantsList.vue b/resources/js/components/ParticipantsList.vue
new file mode 100644
index 0000000..ec14a3f
--- /dev/null
+++ b/resources/js/components/ParticipantsList.vue
@@ -0,0 +1,25 @@
+
+
+
+
+
+ Participants
+
+
+ -
+ {{ participant.name }}
+
+
+
+
diff --git a/resources/js/pages/Dynamics/Show.vue b/resources/js/pages/Dynamics/Show.vue
index faf3bda..fda6607 100644
--- a/resources/js/pages/Dynamics/Show.vue
+++ b/resources/js/pages/Dynamics/Show.vue
@@ -1,7 +1,10 @@
@@ -72,194 +50,17 @@ function submit() {
+
-
-
- Participants
-
-
- -
- {{ participant.name }}
-
-
-
+
+
-
-
-
- Ledgers
-
-
-
-
- No ledgers found for this dynamic.
-
-
-
-
-
-
-
Create a New Ledger
-
-
-
-
-
+
+
diff --git a/resources/js/pages/Ledgers/Show.vue b/resources/js/pages/Ledgers/Show.vue
index c58fdd0..7486df0 100644
--- a/resources/js/pages/Ledgers/Show.vue
+++ b/resources/js/pages/Ledgers/Show.vue
@@ -1,9 +1,10 @@
@@ -248,232 +215,18 @@ function isOwnerUser(userId: number): boolean {
-
-
- Add Mutation
-
-
-
-
-
-
- Mutations
-
-
-
- No mutations found for this ledger.
-
-
+
+
diff --git a/tests/Feature/MutationTest.php b/tests/Feature/MutationTest.php
new file mode 100644
index 0000000..4e78350
--- /dev/null
+++ b/tests/Feature/MutationTest.php
@@ -0,0 +1,117 @@
+create();
+ $dynamic = Dynamic::factory()->create();
+ $dynamic->participants()->attach($owner->id, ['role' => 'owner']);
+ $ledger = Ledger::factory()->create(['dynamic_id' => $dynamic->id, 'score' => 100]);
+
+ $this->actingAs($owner);
+
+ $response = $this->post(route('dynamics.ledgers.mutations.store', [$dynamic, $ledger]), [
+ 'amount' => 15,
+ 'description' => 'Direct point reward',
+ ]);
+
+ $response->assertRedirect(route('dynamics.ledgers.show', [$dynamic, $ledger]));
+
+ $mutation = Mutation::firstWhere('description', 'Direct point reward');
+
+ expect($mutation)->not->toBeNull();
+ expect($mutation->status)->toBe('approved');
+
+ // Score should be updated immediately
+ $ledger->refresh();
+ expect($ledger->score)->toBe(115);
+
+ // Verify chat messages (should NOT say "approved" or "Approved")
+ $mutationChatMessages = $mutation->chat->messages;
+ expect($mutationChatMessages)->toHaveCount(1);
+ expect($mutationChatMessages->first()->content)->toBe("System: Entry was created by {$owner->name}.");
+
+ $dynamicChatMessages = $dynamic->chat->messages;
+ expect($dynamicChatMessages)->toHaveCount(1);
+ expect($dynamicChatMessages->first()->content)->toBe("System: {$owner->name} added entry \"Direct point reward\" for +15 points on \"{$ledger->name}\" ledger.");
+});
+
+test('non-owner participant creates a suggestion which defaults to pending and says suggested', function () {
+ $owner = User::factory()->create();
+ $participant = User::factory()->create();
+ $dynamic = Dynamic::factory()->create();
+ $dynamic->participants()->attach($owner->id, ['role' => 'owner']);
+ $dynamic->participants()->attach($participant->id, ['role' => 'participant']);
+ $ledger = Ledger::factory()->create(['dynamic_id' => $dynamic->id, 'score' => 100]);
+
+ $this->actingAs($participant);
+
+ $response = $this->post(route('dynamics.ledgers.mutations.store', [$dynamic, $ledger]), [
+ 'amount' => 10,
+ 'description' => 'Suggested point reward',
+ ]);
+
+ $response->assertRedirect(route('dynamics.ledgers.show', [$dynamic, $ledger]));
+
+ $mutation = Mutation::firstWhere('description', 'Suggested point reward');
+
+ expect($mutation)->not->toBeNull();
+ expect($mutation->status)->toBe('pending');
+
+ // Score should NOT be updated immediately
+ $ledger->refresh();
+ expect($ledger->score)->toBe(100);
+
+ // Verify chat messages
+ $mutationChatMessages = $mutation->chat->messages;
+ expect($mutationChatMessages)->toHaveCount(1);
+ expect($mutationChatMessages->first()->content)->toBe("System: Suggestion was created by {$participant->name}.");
+
+ $dynamicChatMessages = $dynamic->chat->messages;
+ expect($dynamicChatMessages)->toHaveCount(1);
+ expect($dynamicChatMessages->first()->content)->toBe("System: {$participant->name} suggested \"Suggested point reward\" for +10 points on \"{$ledger->name}\" ledger.");
+});
+
+test('owner can approve a pending suggestion and it is updated and logged', function () {
+ $owner = User::factory()->create();
+ $participant = User::factory()->create();
+ $dynamic = Dynamic::factory()->create();
+ $dynamic->participants()->attach($owner->id, ['role' => 'owner']);
+ $dynamic->participants()->attach($participant->id, ['role' => 'participant']);
+ $ledger = Ledger::factory()->create(['dynamic_id' => $dynamic->id, 'score' => 100]);
+
+ // Create a pending mutation for the participant
+ $mutation = Mutation::factory()->create([
+ 'ledger_id' => $ledger->id,
+ 'user_id' => $participant->id,
+ 'amount' => 20,
+ 'description' => 'Polished dungeon floors',
+ 'status' => 'pending',
+ ]);
+
+ $this->actingAs($owner);
+
+ $response = $this->put(route('dynamics.ledgers.mutations.update', [$dynamic, $ledger, $mutation]), [
+ 'status' => 'approved',
+ ]);
+
+ $response->assertRedirect();
+
+ $mutation->refresh();
+ expect($mutation->status)->toBe('approved');
+
+ $ledger->refresh();
+ expect($ledger->score)->toBe(120);
+
+ // Verify system logs (should have the manual approval log now)
+ $mutationChatMessages = $mutation->chat->messages;
+ // Note: one from boot created (empty or via seeder, but in our factory it starts with 0 messages if not manually logged,
+ // actually our model booted hook creates the chat but doesn't log on boot, the update method creates 1 message)
+ expect($mutationChatMessages->last()->content)->toBe("System: Suggestion was APPROVED by {$owner->name}.");
+
+ $dynamicChatMessages = $dynamic->chat->messages;
+ expect($dynamicChatMessages->last()->content)->toBe("System: {$owner->name} APPROVED the suggestion \"Polished dungeon floors\" for +20 points on \"{$ledger->name}\" ledger.");
+});