further development of the predefinedmutations
Some checks failed
linter / quality (push) Failing after 1m16s
tests / ci (8.3) (push) Failing after 59s
tests / ci (8.4) (push) Failing after 1m14s
tests / ci (8.5) (push) Failing after 1m15s

This commit is contained in:
Daan Meijer 2026-06-17 13:30:55 +02:00
parent ed23bb2a78
commit 11df4ef55c
9 changed files with 145 additions and 49 deletions

View File

@ -3,6 +3,7 @@
namespace App\Http\Controllers;
use App\Models\Dynamic;
use App\Models\Ledger;
use App\Models\PredefinedMutation;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\Request;
@ -15,20 +16,21 @@ class PredefinedMutationController extends Controller
/**
* Display a listing of the resource.
*/
public function index(Dynamic $dynamic)
public function index(Dynamic $dynamic, Ledger $ledger)
{
$this->authorize('update', $dynamic);
return Inertia::render('Dynamics/PredefinedMutations/Index', [
return Inertia::render('Ledgers/PredefinedMutations/Index', [
'dynamic' => $dynamic,
'predefined_mutations' => $dynamic->predefinedMutations()->latest()->get(),
'ledger' => $ledger,
'predefined_mutations' => $ledger->predefinedMutations()->latest()->get(),
]);
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request, Dynamic $dynamic)
public function store(Request $request, Dynamic $dynamic, Ledger $ledger)
{
$this->authorize('update', $dynamic);
@ -36,11 +38,10 @@ class PredefinedMutationController extends Controller
'name' => ['required', 'string', 'max:255'],
'description' => ['nullable', 'string'],
'amount' => ['required', 'integer'],
'type' => ['required', 'string', 'in:reward,penalty'],
]);
$dynamic->predefinedMutations()->create($request->all());
$ledger->predefinedMutations()->create($request->all());
return redirect()->route('dynamics.predefined-mutations.index', $dynamic);
return redirect()->route('dynamics.ledgers.predefined-mutations.index', [$dynamic, $ledger]);
}
}

View File

@ -31,6 +31,11 @@ class Ledger extends Model
return $this->hasMany(Mutation::class);
}
public function predefinedMutations(): HasMany
{
return $this->hasMany(PredefinedMutation::class);
}
public function media(): \Illuminate\Database\Eloquent\Relations\MorphMany
{
return $this->morphMany(Media::class, 'mediable');

View File

@ -11,15 +11,14 @@ class PredefinedMutation extends Model
use HasFactory;
protected $fillable = [
'dynamic_id',
'ledger_id',
'name',
'description',
'amount',
'type',
];
public function dynamic(): BelongsTo
public function ledger(): BelongsTo
{
return $this->belongsTo(Dynamic::class);
return $this->belongsTo(Ledger::class);
}
}

View File

@ -13,11 +13,10 @@ return new class extends Migration
{
Schema::create('predefined_mutations', function (Blueprint $table) {
$table->id();
$table->foreignId('dynamic_id')->constrained()->cascadeOnDelete();
$table->foreignId('ledger_id')->constrained()->cascadeOnDelete();
$table->string('name');
$table->text('description')->nullable();
$table->integer('amount');
$table->string('type')->default('reward');
$table->timestamps();
});
}

View File

@ -70,12 +70,6 @@ function submit() {
</form>
</div>
</div>
<div class="mt-8">
<InertiaLink :href="route('dynamics.predefined-mutations.index', dynamic.id)" class="c-dynamic-settings__submit-btn">
Manage Predefined Mutations
</InertiaLink>
</div>
</div>
</div>
</template>

View File

@ -9,12 +9,15 @@ const props = defineProps<{
id: number;
name: string;
};
ledger: {
id: number;
name: string;
};
predefined_mutations: Array<{
id: number;
name: string;
description: string;
amount: number;
type: string;
}>;
}>();
@ -22,7 +25,6 @@ const form = useForm({
name: '',
description: '',
amount: 0,
type: 'reward',
});
const breadcrumbs = [
@ -34,14 +36,18 @@ const breadcrumbs = [
name: props.dynamic.name,
href: route('dynamics.show', props.dynamic.id),
},
{
name: props.ledger.name,
href: route('dynamics.ledgers.show', [props.dynamic.id, props.ledger.id]),
},
{
name: 'Predefined Mutations',
href: route('dynamics.predefined-mutations.index', props.dynamic.id),
href: route('dynamics.ledgers.predefined-mutations.index', [props.dynamic.id, props.ledger.id]),
},
];
function submit() {
form.post(route('dynamics.predefined-mutations.store', props.dynamic.id), {
form.post(route('dynamics.ledgers.predefined-mutations.store', [props.dynamic.id, props.ledger.id]), {
onSuccess: () => form.reset(),
});
}
@ -56,7 +62,7 @@ function submit() {
<div class="c-predefined-mutations__card">
<div class="c-predefined-mutations__body">
<h3 class="c-predefined-mutations__title">
Predefined Mutations for {{ dynamic.name }}
Predefined Mutations for {{ ledger.name }}
</h3>
<div class="c-predefined-mutations__list">
@ -133,22 +139,6 @@ function submit() {
/>
</div>
<div class="c-predefined-mutations__field">
<label
for="type"
class="c-predefined-mutations__label"
>Type</label
>
<select
v-model="form.type"
id="type"
class="c-predefined-mutations__select"
>
<option value="reward">Reward</option>
<option value="penalty">Penalty</option>
</select>
</div>
<div class="c-predefined-mutations__actions">
<button
type="submit"

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { Head } from '@inertiajs/vue3';
import { Head, Link as InertiaLink } from '@inertiajs/vue3';
import { useEcho } from '@laravel/echo-vue';
import { ref } from 'vue';
import { route } from 'ziggy-js';
@ -168,13 +168,25 @@ function isOwnerUser(userId: number): boolean {
<div class="c-ledger-show__container">
<div class="c-ledger-show__card">
<div class="c-ledger-show__body">
<h3 class="c-ledger-show__title">{{ ledger.name }}</h3>
<p class="c-ledger-show__score">
Score: {{ ledger.score }}
</p>
<p class="c-ledger-show__rules">
{{ ledger.rules }}
</p>
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-4">
<div>
<h3 class="c-ledger-show__title">{{ ledger.name }}</h3>
<p class="c-ledger-show__score">
Score: {{ ledger.score }}
</p>
<p class="c-ledger-show__rules">
{{ ledger.rules }}
</p>
</div>
<div v-if="isOwner" class="flex flex-col gap-2">
<InertiaLink
:href="route('dynamics.ledgers.predefined-mutations.index', [dynamic.id, ledger.id])"
class="c-ledger-show__manage-btn"
>
Predefined Mutations
</InertiaLink>
</div>
</div>
<!-- Ledger Alignment Badge / Subtitle -->
<div class="c-ledger-show__alignment-wrapper">
@ -288,6 +300,10 @@ function isOwnerUser(userId: number): boolean {
@apply py-12;
}
.c-ledger-show__manage-btn {
@apply inline-flex items-center rounded-md border border-transparent bg-gray-800 px-4 py-2 text-xs font-semibold tracking-widest text-white uppercase transition duration-150 ease-in-out hover:bg-gray-700 focus:bg-gray-700 focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 focus:outline-none active:bg-gray-900 dark:bg-gray-200 dark:text-gray-800 dark:hover:bg-white dark:focus:bg-white dark:focus:ring-offset-gray-800 dark:active:bg-gray-300;
}
.c-ledger-show__container {
@apply mx-auto max-w-7xl sm:px-6 lg:px-8;
}

View File

@ -19,7 +19,7 @@ Route::middleware(['auth', 'verified'])->group(function () {
Route::get('/dynamics/{dynamic}/ledgers/create', [LedgerController::class, 'create'])->name('dynamics.ledgers.create');
Route::resource('dynamics.ledgers', LedgerController::class)->scoped()->except(['create']);
Route::resource('dynamics.predefined-mutations', \App\Http\Controllers\PredefinedMutationController::class)->scoped();
Route::resource('dynamics.ledgers.predefined-mutations', \App\Http\Controllers\PredefinedMutationController::class)->scoped();
Route::resource('dynamics.ledgers.mutations', MutationController::class)->scoped();

View File

@ -0,0 +1,92 @@
<?php
use App\Models\User;
use App\Models\Dynamic;
use App\Models\Ledger;
use App\Models\PredefinedMutation;
test('owner can view predefined mutations for ledger', function () {
$owner = User::factory()->create();
$dynamic = Dynamic::factory()->create();
$dynamic->participants()->attach($owner->id, ['role' => 'owner']);
$ledger = Ledger::factory()->create(['dynamic_id' => $dynamic->id]);
$predefined = PredefinedMutation::create([
'ledger_id' => $ledger->id,
'name' => 'Weekly Room Cleaning',
'description' => 'Cleaned up the master bedroom',
'amount' => 20,
]);
$this->actingAs($owner);
$response = $this->get(route('dynamics.ledgers.predefined-mutations.index', [$dynamic->id, $ledger->id]));
$response->assertOk();
$response->assertInertia(fn ($page) => $page
->component('Ledgers/PredefinedMutations/Index')
->where('ledger.id', $ledger->id)
->has('predefined_mutations', 1)
->where('predefined_mutations.0.name', 'Weekly Room Cleaning')
);
});
test('non-owner cannot view predefined mutations for ledger', 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]);
$this->actingAs($participant);
$response = $this->get(route('dynamics.ledgers.predefined-mutations.index', [$dynamic->id, $ledger->id]));
$response->assertStatus(403);
});
test('owner can create predefined mutations for ledger', function () {
$owner = User::factory()->create();
$dynamic = Dynamic::factory()->create();
$dynamic->participants()->attach($owner->id, ['role' => 'owner']);
$ledger = Ledger::factory()->create(['dynamic_id' => $dynamic->id]);
$this->actingAs($owner);
$response = $this->post(route('dynamics.ledgers.predefined-mutations.store', [$dynamic->id, $ledger->id]), [
'name' => 'Polished mirrors',
'description' => 'Mirror polishing in dungeon',
'amount' => 15,
]);
$response->assertRedirect();
$this->assertDatabaseHas('predefined_mutations', [
'ledger_id' => $ledger->id,
'name' => 'Polished mirrors',
'amount' => 15,
]);
});
test('non-owner cannot create predefined mutations for ledger', 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]);
$this->actingAs($participant);
$response = $this->post(route('dynamics.ledgers.predefined-mutations.store', [$dynamic->id, $ledger->id]), [
'name' => 'Polished mirrors',
'description' => 'Mirror polishing in dungeon',
'amount' => 15,
]);
$response->assertStatus(403);
$this->assertDatabaseMissing('predefined_mutations', [
'ledger_id' => $ledger->id,
'name' => 'Polished mirrors',
]);
});