Compare commits

..

No commits in common. "33149e8b799f1b6364d4578af7dcbf8d32630f13" and "7b00d1d3ee302d119251c02f2422dde6b8300e8a" have entirely different histories.

17 changed files with 197 additions and 203 deletions

View File

@ -2,7 +2,6 @@
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;
@ -17,13 +16,4 @@ 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,44 +3,78 @@
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 ?ActivityData $activity = null, public int $id,
public ?SubactivityData $subactivity = null, public ActivityData $activity,
public ?GroupData $group = null, public SubactivityData $subactivity,
public ?DateData $promisedAt = null, public GroupData $group,
public ?DateData $from = null, public ?DateData $promisedAt,
public bool $isActive = false, public DateData $from,
public array $links = [], public bool $isActive,
) {} ) {}
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,
'isActive' => $membership->isActive(), 'is_active' => $membership->isActive(),
'from' => $membership->from, 'from' => $membership->from,
'group' => $membership->group, 'group' => $membership->group,
'promisedAt' => $membership->promised_at, 'promised_at' => $membership->promised_at,
'links' => [
'update' => route('membership.update', $membership),
'destroy' => route('membership.destroy', $membership),
]
]); ]);
} }
/** /**
* @return array<string, mixed> * @return array<string, mixed>
*/ */
public static function memberMeta(Member $member): MembershipMeta public function with(): array
{ {
return MembershipMeta::fromMember($member); return [
// '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

@ -1,41 +0,0 @@
<?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,8 +103,6 @@ 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,10 +1,10 @@
.custom-table { .custom-table {
width: 100%; width: 100%;
& > thead > th, & > thead > tr > th { & > thead > th {
@apply text-left px-6 text-gray-200 font-semibold py-3 border-gray-600 border-b; @apply text-left px-6 text-gray-200 font-semibold py-3 border-gray-600 border-b;
} }
& > tr, & > tbody > tr { & > tr {
@apply text-gray-200 transition-all duration-300 rounded hover:bg-gray-800; @apply text-gray-200 transition-all duration-300 rounded hover:bg-gray-800;
& > td { & > td {
@apply py-1 px-6; @apply py-1 px-6;
@ -12,10 +12,10 @@
} }
&.custom-table-sm { &.custom-table-sm {
& > thead > th, & > thead > tr > th { & > thead > th {
@apply px-3 py-2; @apply px-3 py-2;
} }
& > tr, & > tbody > tr { & > tr {
& > td { & > td {
@apply py-1 px-3; @apply py-1 px-3;
} }
@ -23,11 +23,20 @@
} }
&.custom-table-light { &.custom-table-light {
& > thead > th, & > thead > tr > th { & > thead > th {
@apply border-gray-500; @apply border-gray-500;
} }
& > tr, & > tbody > tr { & > td {
@apply hover:bg-gray-700; &:hover {
@apply bg-gray-700;
} }
} }
} }
}
.custom-table > * {
display: table-row;
}
.custom-table > * > * {
display: table-cell;
}

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 v-if="label" :required="required" :value="label"></f-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 v-if="hint" :value="hint"></f-hint>
</div> </div>
</label> </label>
</template> </template>
<script lang="ts" setup> <script 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,6 +59,11 @@ const props = defineProps({
default: '--kein--', default: '--kein--',
type: String, type: String,
}, },
def: {
required: false,
type: Number,
default: -1,
},
name: { name: {
required: true, required: true,
}, },

View File

@ -1,14 +1,28 @@
<template> <template>
<a v-tooltip="tooltip" :href="href" :target="blank ? '_BLANK' : '_SELF'" class="inline-flex btn btn-sm"> <a v-tooltip="tooltip" :href="href" :target="blank ? '_BLANK' : '_SELF'" class="inline-flex btn btn-sm">
<ui-sprite :src="icon" /> <ui-sprite :src="icon"></ui-sprite>
</a> </a>
</template> </template>
<script lang="ts" setup> <script setup>
const {tooltip, icon, blank = false, href = '#'} = defineProps<{ defineProps({
tooltip: string, tooltip: {
href?: string, required: true,
blank?: boolean, type: String,
icon: string, },
}>(); href: {
type: String,
default: () => '#',
required: false,
},
blank: {
type: Boolean,
default: () => false,
required: false,
},
icon: {
type: String,
required: true,
},
});
</script> </script>

View File

@ -2,5 +2,6 @@
<svg v-bind="$attrs" class="fill-current"><use :xlink:href="`/sprite.svg#${$attrs.src}`" /></svg> <svg v-bind="$attrs" class="fill-current"><use :xlink:href="`/sprite.svg#${$attrs.src}`" /></svg>
</template> </template>
<script lang="ts" setup> <script>
export default {};
</script> </script>

View File

@ -1,73 +1,84 @@
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<D, M extends Custom.PageMetadata>(firstUrl, siteName = null) { export function useApiIndex(firstUrl, siteName = null) {
const axios = inject<Axios>('axios'); const axios = inject('axios');
if (siteName !== null) { if (siteName !== null) {
var {startListener, stopListener} = useQueueEvents(siteName, () => reload()); var {startListener, stopListener} = useQueueEvents(siteName, () => reload());
} }
const single: Ref<D|null> = ref(null); const single = ref(null);
const url = ref(firstUrl); const url = ref(firstUrl);
const inner: {data: Ref<D[]|null>, meta: Ref<M|null>} = { const inner = {
data: ref(null), data: ref([]),
meta: ref(null), meta: ref({}),
}; };
async function reload(resetPage = true, p = {}) { async function reload(resetPage = true, p = {}) {
const params = { var params = {
page: resetPage ? 1 : inner.meta.value?.current_page, page: resetPage ? 1 : inner.meta.value.current_page,
...p, ...p,
}; };
const response = (await axios.get(url.value, {params})).data; var 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: number, p = {}) { async function reloadPage(page, p = {}) {
if (inner.meta.value?.current_page) {
inner.meta.value.current_page = 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: D) { function edit(model) {
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: D) { async function remove(model) {
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: string) { function updateUrl(newUrl) {
url.value = newUrl; url.value = newUrl;
} }
@ -84,6 +95,8 @@ export function useApiIndex<D, M extends Custom.PageMetadata>(firstUrl, siteName
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 id="completed_at" v-model="single.completed_at" type="date" label="Datum" required></f-text>
<f-select id="course_id" v-model="single.course_id" name="course_id" :options="meta.courses" label="Baustein" required /> <f-select id="course_id" v-model="single.course_id" name="course_id" :options="meta.courses" label="Baustein" required></f-select>
<f-text id="event_name" v-model="single.event_name" label="Veranstaltung" required /> <f-text id="event_name" v-model="single.event_name" label="Veranstaltung" required></f-text>
<f-text id="organizer" v-model="single.organizer" label="Veranstalter" required /> <f-text id="organizer" v-model="single.organizer" label="Veranstalter" required></f-text>
<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 v-text="course.course_name"></td>
<td v-text="course.event_name" /> <td v-text="course.event_name"></td>
<td v-text="course.organizer" /> <td v-text="course.organizer"></td>
<td v-text="course.completed_at_human" /> <td v-text="course.completed_at_human"></td>
<td class="flex"> <td class="flex">
<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-warning btn-sm" @click.prevent="edit(course)"><ui-sprite src="pencil"></ui-sprite></a>
<a href="#" class="inline-flex btn btn-danger btn-sm" @click.prevent="remove(course)"><ui-sprite src="trash" /></a> <a href="#" class="inline-flex btn btn-danger btn-sm" @click.prevent="remove(course)"><ui-sprite src="trash"></ui-sprite></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.ts'; import { useApiIndex } from '../../composables/useApiIndex.js';
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 title="Zahlungen" @close="$emit('close')"> </page-header>
<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 v-text="position.description"></td>
<td v-text="position.invoice.status" /> <td v-text="position.invoice.status"></td>
<td v-text="position.price_human" /> <td v-text="position.price_human"></td>
</tr> </tr>
</table> </table>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="js" setup>
defineEmits(['close']); defineEmits(['close']);
import { useApiIndex } from '../../composables/useApiIndex.js'; import { useApiIndex } from '../../composables/useApiIndex.js';

View File

@ -6,62 +6,58 @@
</template> </template>
</page-header> </page-header>
<form v-if="single && meta !== null" 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-select id="group_id" v-model="single.group" name="group_id" :options="meta.groups" label="Gruppierung" required /> <f-select id="group_id" v-model="single.group_id" name="group_id" :options="meta.groups" label="Gruppierung" required />
<f-select id="activity_id" v-model="single.activity" name="activity_id" :options="meta.activities" label="Tätigkeit" required /> <f-select id="activity_id" v-model="single.activity_id" name="activity_id" :options="meta.activities" label="Tätigkeit" required />
<f-select v-if="single.activity" <f-select v-if="single.activity_id"
id="subactivity_id" id="subactivity_id"
:model-value="single.subactivity" :model-value="single.subactivity_id"
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.promisedAt !== null" :model-value="single.promised_at !== 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.promisedAt !== null" id="promised_at" v-model="single.promisedAt" type="date" label="Versprechensdatum" size="sm" /> <f-text v-show="displayPromisedAt && single.promised_at !== null" id="promised_at" v-model="single.promised_at" 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>
<tr>
<th>Tätigkeit</th> <th>Tätigkeit</th>
<th>Untertätigkeit</th> <th>Untertätigkeit</th>
<th>Datum</th> <th>Datum</th>
<th>Aktiv</th> <th>Aktiv</th>
<th /> <th />
</tr>
</thead> </thead>
<tbody>
<tr v-for="(membership, index) in data" :key="index"> <tr v-for="(membership, index) in data" :key="index">
<td v-text="membership.activity?.name" /> <td v-text="membership.activity_name" />
<td v-text="membership.subactivity?.name" /> <td v-text="membership.subactivity_name" />
<td v-text="membership.from?.human" /> <td v-text="membership.human_date" />
<td><ui-boolean-display :value="membership.isActive" dark /></td> <td><ui-boolean-display :value="membership.is_active" dark /></td>
<td class="flex space-x-1"> <td class="flex space-x-1">
<a href="#" class="inline-flex btn btn-warning btn-sm" @click.prevent="edit(membership)"><ui-sprite src="pencil" /></a> <a href="#" class="inline-flex btn btn-warning btn-sm" @click.prevent="edit(membership)"><ui-sprite src="pencil" /></a>
<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-danger btn-sm" @click.prevent="remove(membership)"><ui-sprite src="trash" /></a>
</td> </td>
</tr> </tr>
</tbody>
</table> </table>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="js" 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.ts'; import { useApiIndex } from '../../composables/useApiIndex.js';
const props = defineProps({ const props = defineProps({
url: { url: {
@ -69,14 +65,14 @@ const props = defineProps({
required: true, required: true,
}, },
}); });
const { data, meta, reload, single, create, edit, submit, remove } = useApiIndex<App.Member.Data.MembershipData, App.Member.Data.MembershipMeta>(props.url, 'membership'); const { data, meta, reload, single, create, edit, submit, remove } = useApiIndex(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 || !single.value.subactivity) { if (!single.value || !single.value.activity_id || !single.value.subactivity_id) {
return false; return false;
} }

View File

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

View File

@ -444,20 +444,13 @@ id: number;
name: string; name: string;
}; };
export type MembershipData = { export type MembershipData = {
activity: App.Member.Data.ActivityData | null; id: number;
subactivity: App.Member.Data.SubactivityData | null; activity: App.Member.Data.ActivityData;
group: App.Member.Data.GroupData | null; subactivity: App.Member.Data.SubactivityData;
promisedAt: App.Lib.Data.DateData | null; group: App.Member.Data.GroupData;
from: App.Lib.Data.DateData | null; promised_at: App.Lib.Data.DateData | null;
isActive: boolean; from: App.Lib.Data.DateData;
links: Array<any>; is_active: boolean;
};
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,25 +37,14 @@ 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.promisedAt.raw', now()->format('Y-m-d')) ->assertJsonPath('data.0.promised_at.raw', now()->format('Y-m-d'))
->assertJsonPath('data.0.promisedAt.human', now()->format('d.m.Y')) ->assertJsonPath('data.0.promised_at.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']);
@ -66,11 +55,10 @@ it('returns meta', function () {
->create(); ->create();
$this->get("/member/{$membership->member->id}/membership") $this->get("/member/{$membership->member->id}/membership")
->assertNull('meta.default.activity') ->assertNull('meta.default.activity_id')
->assertNull('meta.default.subactivity') ->assertNull('meta.default.subactivity_id')
->assertNull('meta.default.promisedAt') ->assertNull('meta.default.promised_at')
->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)
@ -88,7 +76,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.promisedAt', null); ->assertJsonPath('data.0.promised_at', null);
}); });
@ -100,7 +88,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.isActive', $isActive); ->assertJsonPath('data.0.is_active', $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/types/generated.d.ts", "resources/types/custom.d.ts"] "include": ["**/*", "resources/js/components/components.d.ts"]
} }