over naar uuids, policies, predefined mutations aan kunnen passen
linter / quality (push) Failing after 1m5s
tests / ci (8.4) (push) Failing after 1m7s
tests / ci (8.5) (push) Failing after 1m5s
tests / ci (8.3) (push) Failing after 12m5s

This commit is contained in:
Daan Meijer
2026-06-21 23:09:38 +02:00
parent 06e5600447
commit 1e0782385b
46 changed files with 1287 additions and 160 deletions
+24 -1
View File
@@ -40,6 +40,16 @@ function updateStatus(mutationId: number, status: 'approved' | 'rejected') {
);
}
function voidMutation(mutationId: number) {
useForm({}).put(
route('dynamics.ledgers.mutations.void', {
dynamic: props.dynamicId,
ledger: props.ledgerId,
mutation: mutationId,
}),
);
}
function isOwnerUser(userId: number): boolean {
const participant = props.participants?.find((p) => p.id === userId);
@@ -157,21 +167,30 @@ function getAmountClass(amount: number): string {
<!-- Owner Approve/Reject Actions -->
<div
v-if="isOwner && mutation.status === 'pending'"
v-if="isOwner && (mutation.status === 'pending' || mutation.status === 'approved')"
class="c-mutation-list__actions"
>
<button
v-if="mutation.status === 'pending'"
@click="updateStatus(mutation.id, 'approved')"
class="c-mutation-list__approve-btn"
>
Approve
</button>
<button
v-if="mutation.status === 'pending'"
@click="updateStatus(mutation.id, 'rejected')"
class="c-mutation-list__reject-btn"
>
Reject
</button>
<button
v-if="mutation.status !== 'voided'"
@click="voidMutation(mutation.id)"
class="c-mutation-list__void-btn"
>
Void
</button>
</div>
<Chat :chat="mutation.chat" :dynamic-id="dynamicId" :participants="participants" />
@@ -290,6 +309,10 @@ function getAmountClass(amount: number): string {
@apply inline-flex cursor-pointer items-center rounded bg-red-600 px-3 py-1.5 text-xs font-semibold text-white hover:bg-red-500;
}
.c-mutation-list__void-btn {
@apply inline-flex cursor-pointer items-center rounded bg-gray-600 px-3 py-1.5 text-xs font-semibold text-white hover:bg-gray-500;
}
.c-mutation-list__empty {
@apply mt-4 text-gray-500;
}
+12 -20
View File
@@ -14,10 +14,9 @@ defineOptions({
});
defineProps<{
unreadEntities: Array<{
unreadDynamics: Array<{
id: number;
name: string;
type: 'Dynamic' | 'Ledger';
url: string;
unread_count: number;
context_activities: Array<{
@@ -51,34 +50,27 @@ function formatTime(isoString: string): string {
<div class="c-dashboard__container">
<h2 class="c-dashboard__title">Recent Activity</h2>
<div v-if="unreadEntities.length > 0" class="c-dashboard__grid">
<div v-if="unreadDynamics.length > 0" class="c-dashboard__grid">
<div
v-for="entity in unreadEntities"
:key="`${entity.type}_${entity.id}`"
v-for="dynamic in unreadDynamics"
:key="dynamic.id"
class="c-dashboard__card"
>
<div class="c-dashboard__card-header">
<div class="c-dashboard__entity-meta">
<span
:class="[
'c-dashboard__badge-type',
entity.type === 'Dynamic'
? 'c-dashboard__badge-type--dynamic'
: 'c-dashboard__badge-type--ledger',
]"
>
{{ entity.type }}
<span class="c-dashboard__badge-type c-dashboard__badge-type--dynamic">
Dynamic
</span>
<span class="c-dashboard__unread-count">
{{ entity.unread_count }} New
{{ dynamic.unread_count }} New
</span>
</div>
<Link
:href="entity.url"
:href="dynamic.url"
class="c-dashboard__entity-link"
>
<h3 class="c-dashboard__entity-title">
{{ entity.name }}
{{ dynamic.name }}
</h3>
</Link>
</div>
@@ -86,7 +78,7 @@ function formatTime(isoString: string): string {
<div class="c-dashboard__activity-list">
<!-- Context / Read Activities -->
<div
v-for="activity in entity.context_activities"
v-for="activity in dynamic.context_activities"
:key="activity.id"
class="c-dashboard__activity-item c-dashboard__activity-item--read"
>
@@ -107,7 +99,7 @@ function formatTime(isoString: string): string {
<!-- Unread Separator Line -->
<div
v-if="entity.new_activities.length > 0"
v-if="dynamic.new_activities.length > 0"
class="c-dashboard__divider"
>
<span class="c-dashboard__divider-text"
@@ -117,7 +109,7 @@ function formatTime(isoString: string): string {
<!-- New / Unread Activities -->
<div
v-for="activity in entity.new_activities"
v-for="activity in dynamic.new_activities"
:key="activity.id"
class="c-dashboard__activity-item c-dashboard__activity-item--unread"
>
+8 -4
View File
@@ -1,10 +1,14 @@
<script setup>
<script setup lang="ts">
import { Head, Link } from '@inertiajs/vue3';
import { route } from 'ziggy-js';
defineProps({
dynamics: Array,
});
defineProps<{
dynamics: Array<{
id: string;
name: string;
rules: string;
}>;
}>();
const breadcrumbs = [
{
@@ -0,0 +1,154 @@
<script setup lang="ts">
import { Head, useForm } from '@inertiajs/vue3';
import { route } from 'ziggy-js';
import AppLayout from '@/layouts/AppLayout.vue';
const props = defineProps<{
dynamic: {
id: number;
name: string;
};
predefined_mutation: {
id: number;
name: string;
description: string;
amount: number;
type: string;
};
}>();
const form = useForm({
name: props.predefined_mutation.name,
description: props.predefined_mutation.description,
amount: props.predefined_mutation.amount,
type: props.predefined_mutation.type,
});
const breadcrumbs = [
{
name: 'Dynamics',
href: route('dynamics.index'),
},
{
name: props.dynamic.name,
href: route('dynamics.show', props.dynamic.id),
},
{
name: 'Predefined Mutations',
href: route('dynamics.predefined-mutations.index', props.dynamic.id),
},
{
name: 'Edit',
href: route('dynamics.predefined-mutations.edit', [props.dynamic.id, props.predefined_mutation.id]),
},
];
function submit() {
form.put(route('dynamics.predefined-mutations.update', [props.dynamic.id, props.predefined_mutation.id]));
}
</script>
<template>
<Head title="Edit Predefined Mutation" />
<AppLayout :breadcrumbs="breadcrumbs">
<div class="c-predefined-mutation-edit">
<div class="c-predefined-mutation-edit__container">
<div class="c-predefined-mutation-edit__card">
<div class="c-predefined-mutation-edit__body">
<h3 class="c-predefined-mutation-edit__title">
Edit {{ predefined_mutation.name }}
</h3>
<form @submit.prevent="submit" class="c-predefined-mutation-edit__form">
<div class="c-predefined-mutation-edit__field">
<label for="name" class="c-predefined-mutation-edit__label">Name</label>
<input v-model="form.name" id="name" type="text" class="c-predefined-mutation-edit__input" />
</div>
<div class="c-predefined-mutation-edit__field">
<label for="description" class="c-predefined-mutation-edit__label">Description</label>
<textarea v-model="form.description" id="description" rows="4" class="c-predefined-mutation-edit__textarea"></textarea>
</div>
<div class="c-predefined-mutation-edit__field">
<label for="amount" class="c-predefined-mutation-edit__label">Amount</label>
<input v-model="form.amount" id="amount" type="number" class="c-predefined-mutation-edit__input" />
</div>
<div class="c-predefined-mutation-edit__field">
<label for="type" class="c-predefined-mutation-edit__label">Type</label>
<select v-model="form.type" id="type" class="c-predefined-mutation-edit__select">
<option value="reward">Reward</option>
<option value="penalty">Penalty</option>
</select>
</div>
<div class="c-predefined-mutation-edit__actions">
<button type="submit" :disabled="form.processing" class="c-predefined-mutation-edit__submit-btn">
Save
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</AppLayout>
</template>
<style scoped>
@reference "../../../../css/app.css";
.c-predefined-mutation-edit {
@apply py-12;
}
.c-predefined-mutation-edit__container {
@apply mx-auto max-w-7xl sm:px-6 lg:px-8;
}
.c-predefined-mutation-edit__card {
@apply overflow-hidden bg-white shadow-sm sm:rounded-lg dark:bg-gray-800;
}
.c-predefined-mutation-edit__body {
@apply p-6 text-gray-900 dark:text-gray-100;
}
.c-predefined-mutation-edit__title {
@apply text-lg font-medium;
}
.c-predefined-mutation-edit__form {
@apply mt-6 space-y-6;
}
.c-predefined-mutation-edit__field {
@apply block;
}
.c-predefined-mutation-edit__label {
@apply block text-sm font-medium text-gray-700 dark:text-gray-300;
}
.c-predefined-mutation-edit__input {
@apply mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 dark:focus:border-indigo-600 dark:focus:ring-indigo-600;
}
.c-predefined-mutation-edit__textarea {
@apply mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 dark:focus:border-indigo-600 dark:focus:ring-indigo-600;
}
.c-predefined-mutation-edit__select {
@apply mt-1 block w-full rounded-md border-gray-300 bg-white p-2 text-sm shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 dark:focus:border-indigo-600 dark:focus:ring-indigo-600;
}
.c-predefined-mutation-edit__actions {
@apply flex items-center gap-4;
}
.c-predefined-mutation-edit__submit-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;
}
</style>
@@ -0,0 +1,266 @@
<script setup lang="ts">
import { Head, useForm, Link } from '@inertiajs/vue3';
import { route } from 'ziggy-js';
import AppLayout from '@/layouts/AppLayout.vue';
const props = defineProps<{
dynamic: {
id: number;
name: string;
};
predefined_mutations: Array<{
id: number;
name: string;
description: string;
amount: number;
type: string;
}>;
}>();
const form = useForm({
name: '',
description: '',
amount: 0,
type: 'reward',
});
const breadcrumbs = [
{
name: 'Dynamics',
href: route('dynamics.index'),
},
{
name: props.dynamic.name,
href: route('dynamics.show', props.dynamic.id),
},
{
name: 'Predefined Mutations',
href: route('dynamics.predefined-mutations.index', props.dynamic.id),
},
];
function submit() {
form.post(route('dynamics.predefined-mutations.store', props.dynamic.id), {
onSuccess: () => form.reset(),
});
}
</script>
<template>
<Head title="Predefined Mutations" />
<AppLayout :breadcrumbs="breadcrumbs">
<div class="c-predefined-mutations">
<div class="c-predefined-mutations__container">
<div class="c-predefined-mutations__card">
<div class="c-predefined-mutations__body">
<h3 class="c-predefined-mutations__title">
Predefined Mutations for {{ dynamic.name }}
</h3>
<div class="c-predefined-mutations__list">
<div
v-for="mutation in predefined_mutations"
:key="mutation.id"
class="c-predefined-mutations__item"
>
<div class="c-predefined-mutations__item-details">
<h4 class="c-predefined-mutations__item-name">
{{ mutation.name }}
</h4>
<p class="c-predefined-mutations__item-description">
{{ mutation.description }}
</p>
</div>
<div class="c-predefined-mutations__item-amount">
{{ mutation.amount }}
</div>
<div class="c-predefined-mutations__item-actions">
<Link :href="route('dynamics.predefined-mutations.edit', [dynamic.id, mutation.id])" class="c-predefined-mutations__item-action-btn">
Edit
</Link>
<Link :href="route('dynamics.predefined-mutations.destroy', [dynamic.id, mutation.id])" method="delete" as="button" class="c-predefined-mutations__item-action-btn c-predefined-mutations__item-action-btn--danger">
Delete
</Link>
</div>
</div>
</div>
</div>
</div>
<div class="c-predefined-mutations__card mt-8">
<div class="c-predefined-mutations__body">
<h3 class="c-predefined-mutations__title">
Create New Predefined Mutation
</h3>
<form
@submit.prevent="submit"
class="c-predefined-mutations__form"
>
<div class="c-predefined-mutations__field">
<label
for="name"
class="c-predefined-mutations__label"
>Name</label
>
<input
v-model="form.name"
id="name"
type="text"
class="c-predefined-mutations__input"
/>
</div>
<div class="c-predefined-mutations__field">
<label
for="description"
class="c-predefined-mutations__label"
>Description</label
>
<textarea
v-model="form.description"
id="description"
rows="4"
class="c-predefined-mutations__textarea"
></textarea>
</div>
<div class="c-predefined-mutations__field">
<label
for="amount"
class="c-predefined-mutations__label"
>Amount</label
>
<input
v-model="form.amount"
id="amount"
type="number"
class="c-predefined-mutations__input"
/>
</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"
:disabled="form.processing"
class="c-predefined-mutations__submit-btn"
>
Create
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</AppLayout>
</template>
<style scoped>
@reference "../../../../css/app.css";
.c-predefined-mutations {
@apply py-12;
}
.c-predefined-mutations__container {
@apply mx-auto max-w-7xl sm:px-6 lg:px-8;
}
.c-predefined-mutations__card {
@apply overflow-hidden bg-white shadow-sm sm:rounded-lg dark:bg-gray-800;
}
.c-predefined-mutations__body {
@apply p-6 text-gray-900 dark:text-gray-100;
}
.c-predefined-mutations__title {
@apply text-lg font-medium;
}
.c-predefined-mutations__list {
@apply mt-6 space-y-4;
}
.c-predefined-mutations__item {
@apply flex items-center justify-between rounded-lg border p-4 dark:border-gray-700;
}
.c-predefined-mutations__item-details {
@apply flex-1;
}
.c-predefined-mutations__item-name {
@apply font-semibold;
}
.c-predefined-mutations__item-description {
@apply text-sm text-gray-600 dark:text-gray-400;
}
.c-predefined-mutations__item-amount {
@apply text-lg font-semibold;
}
.c-predefined-mutations__item-actions {
@apply flex gap-2;
}
.c-predefined-mutations__item-action-btn {
@apply inline-flex items-center rounded-md border border-transparent bg-gray-800 px-3 py-1.5 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-predefined-mutations__item-action-btn--danger {
@apply bg-red-600 hover:bg-red-500 focus:bg-red-500 active:bg-red-700 dark:bg-red-500 dark:hover:bg-red-400 dark:focus:bg-red-400 dark:active:bg-red-600;
}
.c-predefined-mutations__form {
@apply mt-6 space-y-6;
}
.c-predefined-mutations__field {
@apply block;
}
.c-predefined-mutations__label {
@apply block text-sm font-medium text-gray-700 dark:text-gray-300;
}
.c-predefined-mutations__input {
@apply mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 dark:focus:border-indigo-600 dark:focus:ring-indigo-600;
}
.c-predefined-mutations__textarea {
@apply mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 dark:focus:border-indigo-600 dark:focus:ring-indigo-600;
}
.c-predefined-mutations__select {
@apply mt-1 block w-full rounded-md border-gray-300 bg-white p-2 text-sm shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 dark:focus:border-indigo-600 dark:focus:ring-indigo-600;
}
.c-predefined-mutations__actions {
@apply flex items-center gap-4;
}
.c-predefined-mutations__submit-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;
}
</style>
+145
View File
@@ -0,0 +1,145 @@
<script setup lang="ts">
import { Head, useForm } from '@inertiajs/vue3';
import { route } from 'ziggy-js';
import AppLayout from '@/layouts/AppLayout.vue';
const props = defineProps<{
dynamic: {
id: number;
name: string;
};
ledger: {
id: number;
name: string;
rules: string;
};
}>();
const form = useForm({
name: props.ledger.name,
rules: props.ledger.rules,
});
const breadcrumbs = [
{
name: 'Dynamics',
href: route('dynamics.index'),
},
{
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: 'Edit',
href: route('dynamics.ledgers.edit', [props.dynamic.id, props.ledger.id]),
},
];
function submit() {
form.put(route('dynamics.ledgers.update', [props.dynamic.id, props.ledger.id]));
}
</script>
<template>
<Head title="Edit Ledger" />
<AppLayout :breadcrumbs="breadcrumbs">
<div class="c-ledger-edit">
<div class="c-ledger-edit__container">
<div class="c-ledger-edit__card">
<div class="c-ledger-edit__body">
<h3 class="c-ledger-edit__title">
Edit {{ ledger.name }}
</h3>
<form @submit.prevent="submit" class="c-ledger-edit__form">
<div class="c-ledger-edit__field">
<label for="name" class="c-ledger-edit__label">Name</label>
<input v-model="form.name" id="name" type="text" class="c-ledger-edit__input" />
</div>
<div class="c-ledger-edit__field">
<label for="rules" class="c-ledger-edit__label">Rules</label>
<textarea v-model="form.rules" id="rules" rows="4" class="c-ledger-edit__textarea"></textarea>
</div>
<div class="c-ledger-edit__actions">
<button type="submit" :disabled="form.processing" class="c-ledger-edit__submit-btn">
Save
</button>
<Link
:href="route('dynamics.ledgers.close', [dynamic.id, ledger.id])"
method="put"
as="button"
class="c-ledger-edit__submit-btn c-ledger-edit__submit-btn--danger"
>
Close Ledger
</Link>
</div>
</form>
</div>
</div>
</div>
</div>
</AppLayout>
</template>
<style scoped>
@reference "../../../css/app.css";
.c-ledger-edit {
@apply py-12;
}
.c-ledger-edit__container {
@apply mx-auto max-w-7xl sm:px-6 lg:px-8;
}
.c-ledger-edit__card {
@apply overflow-hidden bg-white shadow-sm sm:rounded-lg dark:bg-gray-800;
}
.c-ledger-edit__body {
@apply p-6 text-gray-900 dark:text-gray-100;
}
.c-ledger-edit__title {
@apply text-lg font-medium;
}
.c-ledger-edit__form {
@apply mt-6 space-y-6;
}
.c-ledger-edit__field {
@apply block;
}
.c-ledger-edit__label {
@apply block text-sm font-medium text-gray-700 dark:text-gray-300;
}
.c-ledger-edit__input {
@apply mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 dark:focus:border-indigo-600 dark:focus:ring-indigo-600;
}
.c-ledger-edit__textarea {
@apply mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300 dark:focus:border-indigo-600 dark:focus:ring-indigo-600;
}
.c-ledger-edit__actions {
@apply flex items-center gap-4;
}
.c-ledger-edit__submit-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-edit__submit-btn--danger {
@apply bg-red-600 hover:bg-red-500 focus:bg-red-500 active:bg-red-700 dark:bg-red-500 dark:hover:bg-red-400 dark:focus:bg-red-400 dark:active:bg-red-600;
}
</style>
+6
View File
@@ -212,6 +212,12 @@ function isOwnerUser(userId: number): boolean {
>
Predefined Mutations
</InertiaLink>
<InertiaLink
:href="route('dynamics.ledgers.edit', [dynamic.id, ledger.id])"
class="c-ledger-show__manage-btn"
>
Edit Ledger
</InertiaLink>
</div>
</div>