--wip-- [skip ci]

This commit is contained in:
philipp lang 2025-06-12 21:18:43 +02:00
parent 50878a9a3c
commit 33149e8b79
13 changed files with 186 additions and 156 deletions

View File

@ -2,6 +2,7 @@
namespace App\Member\Data; namespace App\Member\Data;
use App\Group;
use Spatie\LaravelData\Data; use Spatie\LaravelData\Data;
use Spatie\LaravelData\Attributes\MapInputName; use Spatie\LaravelData\Attributes\MapInputName;
use Spatie\LaravelData\Attributes\MapOutputName; use Spatie\LaravelData\Attributes\MapOutputName;
@ -16,4 +17,13 @@ class GroupData extends Data {
public string $name, public string $name,
) {} ) {}
public static function fromId(int $id): static {
$group = Group::findOrFail($id);
return static::from([
'name' => $group->name,
'id' => $group->id,
]);
}
} }

View File

@ -3,78 +3,44 @@
namespace App\Member\Data; namespace App\Member\Data;
use Spatie\LaravelData\Data; use Spatie\LaravelData\Data;
use Spatie\LaravelData\Attributes\MapInputName;
use Spatie\LaravelData\Attributes\MapOutputName;
use Spatie\LaravelData\Mappers\SnakeCaseMapper;
use App\Member\Membership; use App\Member\Membership;
use App\Member\Member; use App\Member\Member;
use App\Activity;
use App\Lib\Data\DateData; use App\Lib\Data\DateData;
#[MapInputName(SnakeCaseMapper::class)]
#[MapOutputName(SnakeCaseMapper::class)]
class MembershipData extends Data class MembershipData extends Data
{ {
public function __construct( public function __construct(
public int $id, public ?ActivityData $activity = null,
public ActivityData $activity, public ?SubactivityData $subactivity = null,
public SubactivityData $subactivity, public ?GroupData $group = null,
public GroupData $group, public ?DateData $promisedAt = null,
public ?DateData $promisedAt, public ?DateData $from = null,
public DateData $from, public bool $isActive = false,
public bool $isActive, public array $links = [],
) {} ) {}
public static function fromModel(Membership $membership): static public static function fromModel(Membership $membership): static
{ {
return static::factory()->withoutMagicalCreation()->from([ return static::factory()->withoutMagicalCreation()->from([
'id' => $membership->id,
'activity' => $membership->activity, 'activity' => $membership->activity,
'subactivity' => $membership->subactivity, 'subactivity' => $membership?->subactivity,
'is_active' => $membership->isActive(), 'isActive' => $membership->isActive(),
'from' => $membership->from, 'from' => $membership->from,
'group' => $membership->group, 'group' => $membership->group,
'promised_at' => $membership->promised_at, 'promisedAt' => $membership->promised_at,
'links' => [
'update' => route('membership.update', $membership),
'destroy' => route('membership.destroy', $membership),
]
]); ]);
} }
/** /**
* @return array<string, mixed> * @return array<string, mixed>
*/ */
public function with(): array public static function memberMeta(Member $member): MembershipMeta
{ {
return [ return MembershipMeta::fromMember($member);
// 'human_date' => $this->from->format('d.m.Y'),
// 'promised_at_human' => $this->promisedAt?->format('d.m.Y'),
// 'promised_at' => $this->promisedAt?->format('Y-m-d'),
'links' => [
'update' => route('membership.update', ['membership' => $this->id]),
'destroy' => route('membership.destroy', ['membership' => $this->id]),
]
];
}
/**
* @return array<string, mixed>
*/
public static function memberMeta(Member $member): array
{
$activities = Activity::with('subactivities')->get();
return [
'links' => [
'store' => route('member.membership.store', ['member' => $member]),
],
'groups' => NestedGroup::cacheForSelect(),
'activities' => $activities->map(fn($activity) => ['id' => $activity->id, 'name' => $activity->name]),
'subactivities' => $activities->mapWithKeys(fn($activity) => [$activity->id => $activity->subactivities->map(fn($subactivity) => ['id' => $subactivity->id, 'name' => $subactivity->name, 'is_age_group' => $subactivity->is_age_group])]),
'default' => [
'group_id' => $member->group_id,
'activity_id' => null,
'subactivity_id' => null,
'promised_at' => null,
],
];
} }
} }

View File

@ -0,0 +1,41 @@
<?php
namespace App\Member\Data;
use App\Activity;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\Attributes\MapInputName;
use Spatie\LaravelData\Attributes\MapOutputName;
use Spatie\LaravelData\Mappers\SnakeCaseMapper;
use App\Member\Member;
use Illuminate\Support\Collection;
#[MapInputName(SnakeCaseMapper::class)]
#[MapOutputName(SnakeCaseMapper::class)]
class MembershipMeta extends Data
{
public function __construct(
public array $links,
public Collection $groups,
public Collection $activities,
public Collection $subactivities,
public MembershipData $default,
) {}
public static function fromMember(Member $member): static
{
$activities = Activity::with('subactivities')->get();
return static::factory()->withoutMagicalCreation()->from([
'links' => [
'store' => route('member.membership.store', ['member' => $member]),
],
'groups' => NestedGroup::cacheForSelect(),
'activities' => $activities->map(fn($activity) => ['id' => $activity->id, 'name' => $activity->name]),
'subactivities' => $activities->mapWithKeys(fn($activity) => [$activity->id => $activity->subactivities->map(fn($subactivity) => ['id' => $subactivity->id, 'name' => $subactivity->name, 'is_age_group' => $subactivity->is_age_group])]),
'default' => MembershipData::from(['group' => $member->group_id]),
]);
}
}

View File

@ -103,6 +103,8 @@ services:
- ./data/redis:/data - ./data/redis:/data
meilisearch: meilisearch:
ports:
- "7700:7700"
image: getmeili/meilisearch:v1.6 image: getmeili/meilisearch:v1.6
volumes: volumes:
- ./data/meilisearch:/meili_data - ./data/meilisearch:/meili_data

View File

@ -1,17 +1,17 @@
<template> <template>
<label class="flex flex-col group" :for="id" :class="sizeClass(size)"> <label class="flex flex-col group" :for="id" :class="sizeClass(size)">
<f-label v-if="label" :required="required" :value="label"></f-label> <f-label v-if="label" :required="required" :value="label" />
<div class="relative flex-none flex"> <div class="relative flex-none flex">
<select v-model="inner" :disabled="disabled" :name="name" :class="[fieldHeight, fieldAppearance, selectAppearance]"> <select v-model="inner" :disabled="disabled" :name="name" :class="[fieldHeight, fieldAppearance, selectAppearance]">
<option v-if="placeholder" :value="def">{{ placeholder }}</option> <option v-if="placeholder" :value="def">{{ placeholder }}</option>
<option v-for="option in parsedOptions" :key="option.id" :value="option.id">{{ option.name }}</option> <option v-for="option in parsedOptions" :key="option.id" :value="option.id">{{ option.name }}</option>
</select> </select>
<f-hint v-if="hint" :value="hint"></f-hint> <f-hint v-if="hint" :value="hint" />
</div> </div>
</label> </label>
</template> </template>
<script setup> <script lang="ts" setup>
import {computed, ref} from 'vue'; import {computed, ref} from 'vue';
import useFieldSize from '../../composables/useFieldSize.js'; import useFieldSize from '../../composables/useFieldSize.js';
import map from 'lodash/map'; import map from 'lodash/map';
@ -59,11 +59,6 @@ const props = defineProps({
default: '--kein--', default: '--kein--',
type: String, type: String,
}, },
def: {
required: false,
type: Number,
default: -1,
},
name: { name: {
required: true, required: true,
}, },
@ -79,8 +74,8 @@ const parsedOptions = computed(() => {
return Array.isArray(props.options) return Array.isArray(props.options)
? props.options ? props.options
: map(props.options, (value, key) => { : map(props.options, (value, key) => {
return {name: value, id: key}; return {name: value, id: key};
}); });
}); });
const def = ref('iu1Feixah5AeKai3ewooJahjeaegee0eiD4maeth1oul4Hei7u'); const def = ref('iu1Feixah5AeKai3ewooJahjeaegee0eiD4maeth1oul4Hei7u');

View File

@ -1,84 +1,73 @@
import {ref, inject, onBeforeUnmount} from 'vue'; import {ref, inject, onBeforeUnmount} from 'vue';
import {router} from '@inertiajs/vue3'; import {router} from '@inertiajs/vue3';
import type {Ref} from 'vue';
import useQueueEvents from './useQueueEvents.js'; import useQueueEvents from './useQueueEvents.js';
import { Axios } from 'axios';
export function useApiIndex(firstUrl, siteName = null) { export function useApiIndex<D, M extends Custom.PageMetadata>(firstUrl, siteName = null) {
const axios = inject('axios'); const axios = inject<Axios>('axios');
if (siteName !== null) { if (siteName !== null) {
var {startListener, stopListener} = useQueueEvents(siteName, () => reload()); var {startListener, stopListener} = useQueueEvents(siteName, () => reload());
} }
const single = ref(null); const single: Ref<D|null> = ref(null);
const url = ref(firstUrl); const url = ref(firstUrl);
const inner = { const inner: {data: Ref<D[]|null>, meta: Ref<M|null>} = {
data: ref([]), data: ref(null),
meta: ref({}), meta: ref(null),
}; };
async function reload(resetPage = true, p = {}) { async function reload(resetPage = true, p = {}) {
var params = { const params = {
page: resetPage ? 1 : inner.meta.value.current_page, page: resetPage ? 1 : inner.meta.value?.current_page,
...p, ...p,
}; };
var response = (await axios.get(url.value, {params})).data; const response = (await axios.get(url.value, {params})).data;
inner.data.value = response.data; inner.data.value = response.data;
inner.meta.value = response.meta; inner.meta.value = response.meta;
} }
async function reloadPage(page, p = {}) { async function reloadPage(page: number, p = {}) {
inner.meta.value.current_page = page; if (inner.meta.value?.current_page) {
inner.meta.value.current_page = page;
}
await reload(false, p); await reload(false, p);
} }
function create() { function create() {
single.value = JSON.parse(JSON.stringify(inner.meta.value.default)); single.value = JSON.parse(JSON.stringify(inner.meta.value?.default));
} }
function edit(model) { function edit(model: D) {
single.value = JSON.parse(JSON.stringify(model)); single.value = JSON.parse(JSON.stringify(model));
} }
async function submit() { async function submit() {
if (single.value === null) {
return;
}
single.value.id ? await axios.patch(single.value.links.update, single.value) : await axios.post(inner.meta.value.links.store, single.value); single.value.id ? await axios.patch(single.value.links.update, single.value) : await axios.post(inner.meta.value.links.store, single.value);
await reload(); await reload();
single.value = null; single.value = null;
} }
async function remove(model) { async function remove(model: D) {
await axios.delete(model.links.destroy); await axios.delete(model.links.destroy);
await reload(); await reload();
} }
function can(permission) {
return inner.meta.value.can[permission];
}
function toFilterString(data) { function toFilterString(data) {
return btoa(encodeURIComponent(JSON.stringify(data))); return btoa(encodeURIComponent(JSON.stringify(data)));
} }
function requestCallback(successMessage, failureMessage) {
return {
onSuccess: () => {
this.$success(successMessage);
reload(false);
},
onFailure: () => {
this.$error(failureMessage);
reload(false);
},
preserveState: true,
};
}
function cancel() { function cancel() {
single.value = null; single.value = null;
} }
function updateUrl(newUrl) { function updateUrl(newUrl: string) {
url.value = newUrl; url.value = newUrl;
} }
@ -95,8 +84,6 @@ export function useApiIndex(firstUrl, siteName = null) {
edit, edit,
reload, reload,
reloadPage, reloadPage,
can,
requestCallback,
router, router,
submit, submit,
remove, remove,

View File

@ -8,10 +8,10 @@
</page-header> </page-header>
<form v-if="single" class="p-6 grid gap-4 justify-start" @submit.prevent="submit"> <form v-if="single" class="p-6 grid gap-4 justify-start" @submit.prevent="submit">
<f-text id="completed_at" v-model="single.completed_at" type="date" label="Datum" required></f-text> <f-text id="completed_at" v-model="single.completed_at" type="date" label="Datum" required />
<f-select id="course_id" v-model="single.course_id" name="course_id" :options="meta.courses" label="Baustein" required></f-select> <f-select id="course_id" v-model="single.course_id" name="course_id" :options="meta.courses" label="Baustein" required />
<f-text id="event_name" v-model="single.event_name" label="Veranstaltung" required></f-text> <f-text id="event_name" v-model="single.event_name" label="Veranstaltung" required />
<f-text id="organizer" v-model="single.organizer" label="Veranstalter" required></f-text> <f-text id="organizer" v-model="single.organizer" label="Veranstalter" required />
<button type="submit" class="btn btn-primary">Absenden</button> <button type="submit" class="btn btn-primary">Absenden</button>
</form> </form>
@ -23,18 +23,18 @@
<th>Veranstaltung</th> <th>Veranstaltung</th>
<th>Veranstalter</th> <th>Veranstalter</th>
<th>Datum</th> <th>Datum</th>
<th></th> <th />
</tr> </tr>
</thead> </thead>
<tr v-for="(course, index) in data" :key="index"> <tr v-for="(course, index) in data" :key="index">
<td v-text="course.course_name"></td> <td v-text="course.course_name" />
<td v-text="course.event_name"></td> <td v-text="course.event_name" />
<td v-text="course.organizer"></td> <td v-text="course.organizer" />
<td v-text="course.completed_at_human"></td> <td v-text="course.completed_at_human" />
<td class="flex"> <td class="flex">
<a href="#" class="inline-flex btn btn-warning btn-sm" @click.prevent="edit(course)"><ui-sprite src="pencil"></ui-sprite></a> <a href="#" class="inline-flex btn btn-warning btn-sm" @click.prevent="edit(course)"><ui-sprite src="pencil" /></a>
<a href="#" class="inline-flex btn btn-danger btn-sm" @click.prevent="remove(course)"><ui-sprite src="trash"></ui-sprite></a> <a href="#" class="inline-flex btn btn-danger btn-sm" @click.prevent="remove(course)"><ui-sprite src="trash" /></a>
</td> </td>
</tr> </tr>
</table> </table>
@ -44,7 +44,7 @@
<script lang="js" setup> <script lang="js" setup>
defineEmits(['close']); defineEmits(['close']);
import { useApiIndex } from '../../composables/useApiIndex.js'; import { useApiIndex } from '../../composables/useApiIndex.ts';
const props = defineProps({ const props = defineProps({
url: { url: {

View File

@ -1,5 +1,5 @@
<template> <template>
<page-header title="Zahlungen" @close="$emit('close')"> </page-header> <page-header title="Zahlungen" @close="$emit('close')" />
<div class="grow"> <div class="grow">
<table class="custom-table custom-table-light custom-table-sm text-sm"> <table class="custom-table custom-table-light custom-table-sm text-sm">
@ -10,15 +10,15 @@
</thead> </thead>
<tr v-for="(position, index) in data" :key="index"> <tr v-for="(position, index) in data" :key="index">
<td v-text="position.description"></td> <td v-text="position.description" />
<td v-text="position.invoice.status"></td> <td v-text="position.invoice.status" />
<td v-text="position.price_human"></td> <td v-text="position.price_human" />
</tr> </tr>
</table> </table>
</div> </div>
</template> </template>
<script lang="js" setup> <script lang="ts" setup>
defineEmits(['close']); defineEmits(['close']);
import { useApiIndex } from '../../composables/useApiIndex.js'; import { useApiIndex } from '../../composables/useApiIndex.js';

View File

@ -6,58 +6,62 @@
</template> </template>
</page-header> </page-header>
<form v-if="single" class="p-6 grid gap-4 justify-start" @submit.prevent="submit"> <form v-if="single && meta !== null" class="p-6 grid gap-4 justify-start" @submit.prevent="submit">
<f-select id="group_id" v-model="single.group_id" name="group_id" :options="meta.groups" label="Gruppierung" required /> <f-select id="group_id" v-model="single.group" name="group_id" :options="meta.groups" label="Gruppierung" required />
<f-select id="activity_id" v-model="single.activity_id" name="activity_id" :options="meta.activities" label="Tätigkeit" required /> <f-select id="activity_id" v-model="single.activity" name="activity_id" :options="meta.activities" label="Tätigkeit" required />
<f-select v-if="single.activity_id" <f-select v-if="single.activity"
id="subactivity_id" id="subactivity_id"
:model-value="single.subactivity_id" :model-value="single.subactivity"
name="subactivity_id" name="subactivity_id"
:options="meta.subactivities[single.activity_id]" :options="meta.subactivities[single.activity.id]"
label="Untertätigkeit" label="Untertätigkeit"
@update:model-value="setSubactivityId(single, $event)" @update:model-value="setSubactivityId(single, $event)"
/> />
<f-switch v-if="displayPromisedAt" <f-switch v-if="displayPromisedAt"
id="has_promise" id="has_promise"
name="has_promise" name="has_promise"
:model-value="single.promised_at !== null" :model-value="single.promisedAt !== null"
label="Hat Versprechen" label="Hat Versprechen"
@update:model-value="setPromisedAtSwitch(single, $event)" @update:model-value="setPromisedAtSwitch(single, $event)"
/> />
<f-text v-show="displayPromisedAt && single.promised_at !== null" id="promised_at" v-model="single.promised_at" type="date" label="Versprechensdatum" size="sm" /> <f-text v-show="displayPromisedAt && single.promisedAt !== null" id="promised_at" v-model="single.promisedAt" type="date" label="Versprechensdatum" size="sm" />
<button type="submit" class="btn btn-primary">Absenden</button> <button type="submit" class="btn btn-primary">Absenden</button>
</form> </form>
<div v-else class="grow"> <div v-else class="grow">
<table class="custom-table custom-table-light custom-table-sm text-sm"> <table class="custom-table custom-table-light custom-table-sm text-sm">
<thead> <thead>
<th>Tätigkeit</th> <tr>
<th>Untertätigkeit</th> <th>Tätigkeit</th>
<th>Datum</th> <th>Untertätigkeit</th>
<th>Aktiv</th> <th>Datum</th>
<th /> <th>Aktiv</th>
<th />
</tr>
</thead> </thead>
<tr v-for="(membership, index) in data" :key="index"> <tbody>
<td v-text="membership.activity_name" /> <tr v-for="(membership, index) in data" :key="index">
<td v-text="membership.subactivity_name" /> <td v-text="membership.activity?.name" />
<td v-text="membership.human_date" /> <td v-text="membership.subactivity?.name" />
<td><ui-boolean-display :value="membership.is_active" dark /></td> <td v-text="membership.from?.human" />
<td class="flex space-x-1"> <td><ui-boolean-display :value="membership.isActive" dark /></td>
<a href="#" class="inline-flex btn btn-warning btn-sm" @click.prevent="edit(membership)"><ui-sprite src="pencil" /></a> <td class="flex space-x-1">
<a href="#" class="inline-flex btn btn-danger btn-sm" @click.prevent="remove(membership)"><ui-sprite src="trash" /></a> <a href="#" class="inline-flex btn btn-warning btn-sm" @click.prevent="edit(membership)"><ui-sprite src="pencil" /></a>
</td> <a href="#" class="inline-flex btn btn-danger btn-sm" @click.prevent="remove(membership)"><ui-sprite src="trash" /></a>
</tr> </td>
</tr>
</tbody>
</table> </table>
</div> </div>
</template> </template>
<script lang="js" setup> <script lang="ts" setup>
import { computed } from 'vue'; import { computed } from 'vue';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
defineEmits(['close']); defineEmits(['close']);
import { useApiIndex } from '../../composables/useApiIndex.js'; import { useApiIndex } from '../../composables/useApiIndex.ts';
const props = defineProps({ const props = defineProps({
url: { url: {
@ -65,14 +69,14 @@ const props = defineProps({
required: true, required: true,
}, },
}); });
const { data, meta, reload, single, create, edit, submit, remove } = useApiIndex(props.url, 'membership'); const { data, meta, reload, single, create, edit, submit, remove } = useApiIndex<App.Member.Data.MembershipData, App.Member.Data.MembershipMeta>(props.url, 'membership');
function setPromisedAtSwitch(single, value) { function setPromisedAtSwitch(single, value) {
single.promised_at = value ? dayjs().format('YYYY-MM-DD') : null; single.promised_at = value ? dayjs().format('YYYY-MM-DD') : null;
} }
const displayPromisedAt = computed(function () { const displayPromisedAt = computed(function () {
if (!single.value || !single.value.activity_id || !single.value.subactivity_id) { if (!single.value || !single.value.activity || !single.value.subactivity) {
return false; return false;
} }

6
resources/types/custom.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
declare namespace Custom {
export type PageMetadata = {
current_page: number;
default: object;
};
}

View File

@ -444,13 +444,20 @@ id: number;
name: string; name: string;
}; };
export type MembershipData = { export type MembershipData = {
id: number; activity: App.Member.Data.ActivityData | null;
activity: App.Member.Data.ActivityData; subactivity: App.Member.Data.SubactivityData | null;
subactivity: App.Member.Data.SubactivityData; group: App.Member.Data.GroupData | null;
group: App.Member.Data.GroupData; promisedAt: App.Lib.Data.DateData | null;
promised_at: App.Lib.Data.DateData | null; from: App.Lib.Data.DateData | null;
from: App.Lib.Data.DateData; isActive: boolean;
is_active: boolean; links: Array<any>;
};
export type MembershipMeta = {
links: Array<any>;
groups: any;
activities: any;
subactivities: any;
default: App.Member.Data.MembershipData;
}; };
export type NestedGroup = { export type NestedGroup = {
id: number; id: number;

View File

@ -37,14 +37,25 @@ it('testItShowsActivityAndSubactivityNamesOfMember', function () {
->assertJsonPath('data.0.subactivity.name', 'Wölfling') ->assertJsonPath('data.0.subactivity.name', 'Wölfling')
->assertJsonPath('data.0.from.human', '02.11.2022') ->assertJsonPath('data.0.from.human', '02.11.2022')
->assertJsonPath('data.0.from.raw', '2022-11-02') ->assertJsonPath('data.0.from.raw', '2022-11-02')
->assertJsonPath('data.0.promised_at.raw', now()->format('Y-m-d')) ->assertJsonPath('data.0.promisedAt.raw', now()->format('Y-m-d'))
->assertJsonPath('data.0.promised_at.human', now()->format('d.m.Y')) ->assertJsonPath('data.0.promisedAt.human', now()->format('d.m.Y'))
->assertJsonPath('data.0.group.id', $group->id) ->assertJsonPath('data.0.group.id', $group->id)
->assertJsonPath('data.0.id', $membership->id)
->assertJsonPath('data.0.links.update', route('membership.update', ['membership' => $membership])) ->assertJsonPath('data.0.links.update', route('membership.update', ['membership' => $membership]))
->assertJsonPath('data.0.links.destroy', route('membership.destroy', ['membership' => $membership])); ->assertJsonPath('data.0.links.destroy', route('membership.destroy', ['membership' => $membership]));
}); });
it('activity and subactivity can be null', function () {
$this->withoutExceptionHandling()->login()->loginNami();
$group = Group::factory()->create(['name' => 'aaaaaaaa']);
$member = Member::factory()
->defaults()
->for($group)
->has(Membership::factory()->for($group)->in('€ Mitglied', 122)->from('2022-11-02')->promise(now()))
->create();
$this->get("/member/{$member->id}/membership")->assertNull('data.0.subactivity');
});
it('returns meta', function () { it('returns meta', function () {
$this->withoutExceptionHandling()->login()->loginNami(); $this->withoutExceptionHandling()->login()->loginNami();
$group = Group::factory()->create(['name' => 'aaaaaaaa']); $group = Group::factory()->create(['name' => 'aaaaaaaa']);
@ -55,10 +66,11 @@ it('returns meta', function () {
->create(); ->create();
$this->get("/member/{$membership->member->id}/membership") $this->get("/member/{$membership->member->id}/membership")
->assertNull('meta.default.activity_id') ->assertNull('meta.default.activity')
->assertNull('meta.default.subactivity_id') ->assertNull('meta.default.subactivity')
->assertNull('meta.default.promised_at') ->assertNull('meta.default.promisedAt')
->assertJsonPath('meta.default.group_id', $group->id) ->assertJsonPath('meta.default.group.id', $group->id)
->assertJsonPath('meta.default.group.name', $group->name)
->assertJsonPath('meta.groups.0.id', $group->id) ->assertJsonPath('meta.groups.0.id', $group->id)
->assertJsonPath('meta.activities.0.id', $membership->activity->id) ->assertJsonPath('meta.activities.0.id', $membership->activity->id)
->assertJsonPath('meta.activities.0.name', $membership->activity->name) ->assertJsonPath('meta.activities.0.name', $membership->activity->name)
@ -76,7 +88,7 @@ it('promised at can be null', function () {
->create(); ->create();
$this->get("/member/{$member->id}/membership") $this->get("/member/{$member->id}/membership")
->assertJsonPath('data.0.promised_at', null); ->assertJsonPath('data.0.promisedAt', null);
}); });
@ -88,7 +100,7 @@ it('testItShowsIfMembershipIsActive', function (Carbon $from, ?Carbon $to, bool
->create(); ->create();
$this->get("/member/{$member->id}/membership") $this->get("/member/{$member->id}/membership")
->assertJsonPath('data.0.is_active', $isActive); ->assertJsonPath('data.0.isActive', $isActive);
})->with([ })->with([
[now()->subMonths(2), null, true], [now()->subMonths(2), null, true],
[now()->subMonths(2), now()->subDay(), false], [now()->subMonths(2), now()->subDay(), false],

View File

@ -59,5 +59,5 @@
"@/*": ["./resources/js/*"] "@/*": ["./resources/js/*"]
} }
}, },
"include": ["**/*", "resources/js/components/components.d.ts"] "include": ["**/*", "resources/types/generated.d.ts", "resources/types/custom.d.ts"]
} }