Skip to content

Commit

Permalink
Merge pull request #168 from codeanker/feature/gh-125
Browse files Browse the repository at this point in the history
Custom Fields in Unterveranstaltung
  • Loading branch information
superbarne authored Nov 24, 2024
2 parents 6dd90cd + ea647a2 commit 0bde3c6
Show file tree
Hide file tree
Showing 23 changed files with 519 additions and 298 deletions.
2 changes: 2 additions & 0 deletions api/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import type { inferRouterInputs, inferRouterOutputs } from '@trpc/server'

import type { appRouter } from './index'

export * from '@prisma/client'

export * from './enumMappings/index'
export type AppRouter = typeof appRouter
export type { TKontaktSchema } from './services/kontakt/schema/kontakt.schema'
Expand Down
1 change: 1 addition & 0 deletions api/src/services/anmeldung/anmeldungVerwaltungGet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export const anmeldungVerwaltungGetProcedure = defineProcedure({
},
unterveranstaltung: {
select: {
id: true,
veranstaltung: {
select: {
id: true,
Expand Down
2 changes: 2 additions & 0 deletions api/src/services/customFields/customFields.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { mergeRouters } from '../../trpc'

import { customFieldsGet } from './customFieldsGet'
import { customFieldsList } from './customFieldsList'
import { customFieldsUnterveranstaltungCreate } from './customFieldsUnterveranstaltungCreate'
import { customFieldsVeranstaltungCreate } from './customFieldsVeranstaltungCreate'
import { customFieldsVeranstaltungDelete } from './customFieldsVeranstaltungDelete'
import { customFieldsVeranstaltungUpdate } from './customFieldsVeranstaltungUpdate'
Expand All @@ -15,6 +16,7 @@ export const customFieldsRouter = mergeRouters(
customFieldsVeranstaltungCreate.router,
customFieldsVeranstaltungUpdate.router,
customFieldsVeranstaltungDelete.router,
customFieldsUnterveranstaltungCreate.router,
customFieldValuesUpdate.router,
// Add Routes here - do not delete this line
)
11 changes: 4 additions & 7 deletions api/src/services/customFields/customFieldsList.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { CustomField } from '@prisma/client'
import { z } from 'zod'

import prisma from '../../prisma'
Expand All @@ -9,12 +8,10 @@ export const customFieldsList = defineProcedure({
method: 'query',
protection: { type: 'public' },
inputSchema: z.strictObject({
entity: z.enum(['veranstaltung', 'unterveranstaltung']),
entity: z.enum(['veranstaltung', 'unterveranstaltung']).optional(),
entityId: z.number(),
}),
async handler({ input }) {
let fields: CustomField[] = []

if (input.entity === 'veranstaltung') {
const veranstaltung = await prisma.veranstaltung.findUniqueOrThrow({
where: {
Expand All @@ -25,7 +22,7 @@ export const customFieldsList = defineProcedure({
},
})

fields = veranstaltung.customFields
return veranstaltung.customFields
} else if (input.entity === 'unterveranstaltung') {
const ausschreibung = await prisma.unterveranstaltung.findUniqueOrThrow({
where: {
Expand All @@ -41,9 +38,9 @@ export const customFieldsList = defineProcedure({
},
})

fields = fields.concat(...ausschreibung.veranstaltung.customFields, ...ausschreibung.customFields)
return [...ausschreibung.veranstaltung.customFields, ...ausschreibung.customFields]
}

return fields
return []
},
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Role } from '@prisma/client'
import { z } from 'zod'

import prisma from '../../prisma'
import { defineProcedure } from '../../types/defineProcedure'

import { customFieldSchema } from './schema/customField.schema'

export const customFieldsUnterveranstaltungCreate = defineProcedure({
key: 'unterveranstaltungCreate',
method: 'mutation',
protection: { type: 'restrictToRoleIds', roleIds: [Role.ADMIN, Role.GLIEDERUNG_ADMIN] },
inputSchema: z.strictObject({
unterveranstaltungId: z.number(),
data: customFieldSchema,
}),
handler: ({ input }) =>
prisma.customField.create({
data: {
...input.data,
unterveranstaltungId: input.unterveranstaltungId,
},
}),
})
15 changes: 5 additions & 10 deletions api/src/services/customFields/customFieldsVeranstaltungCreate.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
import { CustomFieldPosition, CustomFieldType, Role } from '@prisma/client'
import { Role } from '@prisma/client'
import { z } from 'zod'

import prisma from '../../prisma'
import { defineProcedure } from '../../types/defineProcedure'

import { customFieldSchema } from './schema/customField.schema'

export const customFieldsVeranstaltungCreate = defineProcedure({
key: 'verwaltungCreate',
key: 'veranstaltungCreate',
method: 'mutation',
protection: { type: 'restrictToRoleIds', roleIds: [Role.ADMIN] },
inputSchema: z.strictObject({
veranstaltungId: z.number(),
data: z.strictObject({
name: z.string().min(1),
description: z.string().nullable(),
type: z.nativeEnum(CustomFieldType),
required: z.boolean(),
options: z.array(z.string()),
positions: z.nativeEnum(CustomFieldPosition).array(),
}),
data: customFieldSchema,
}),
async handler({ input }) {
return await prisma.customField.create({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import prisma from '../../prisma'
import { defineProcedure } from '../../types/defineProcedure'

export const customFieldsVeranstaltungDelete = defineProcedure({
key: 'verwaltungDelete',
key: 'veranstaltungDelete',
method: 'mutation',
protection: { type: 'restrictToRoleIds', roleIds: [Role.ADMIN] },
inputSchema: z.strictObject({
Expand Down
15 changes: 5 additions & 10 deletions api/src/services/customFields/customFieldsVeranstaltungUpdate.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
import { CustomFieldPosition, CustomFieldType, Role } from '@prisma/client'
import { Role } from '@prisma/client'
import { z } from 'zod'

import prisma from '../../prisma'
import { defineProcedure } from '../../types/defineProcedure'

import { customFieldSchema } from './schema/customField.schema'

export const customFieldsVeranstaltungUpdate = defineProcedure({
key: 'verwaltungUpdate',
key: 'veranstaltungUpdate',
method: 'mutation',
protection: { type: 'restrictToRoleIds', roleIds: [Role.ADMIN] },
inputSchema: z.strictObject({
fieldId: z.number(),
data: z.strictObject({
name: z.string().min(1),
description: z.string().nullable(),
type: z.nativeEnum(CustomFieldType),
required: z.boolean(),
options: z.array(z.string()),
positions: z.nativeEnum(CustomFieldPosition).array(),
}),
data: customFieldSchema,
}),
async handler({ input }) {
return await prisma.customField.update({
Expand Down
11 changes: 11 additions & 0 deletions api/src/services/customFields/schema/customField.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { CustomFieldPosition, CustomFieldType } from '@prisma/client'
import { z } from 'zod'

export const customFieldSchema = z.strictObject({
name: z.string().min(1),
description: z.string().nullable(),
type: z.nativeEnum(CustomFieldType),
required: z.boolean(),
options: z.array(z.string()),
positions: z.nativeEnum(CustomFieldPosition).array(),
})
42 changes: 28 additions & 14 deletions frontend/src/components/AnmeldungenTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import BasicGrid from './BasicGrid.vue'
import { apiClient } from '@/api'
import AnmeldungStatusSelect from '@/components/AnmeldungStatusSelect.vue'
import CustomFieldsForm from '@/components/CustomFields/CustomFieldsForm.vue'
import CustomFieldsFormUser from '@/components/CustomFields/CustomFieldsFormUser.vue'
import FormPersonGeneral, { type FormPersonGeneralSubmit } from '@/components/forms/person/FormPersonGeneral.vue'
import Drawer from '@/components/LayoutComponents/Drawer.vue'
import Notification from '@/components/LayoutComponents/Notifications.vue'
Expand Down Expand Up @@ -99,12 +99,6 @@ const { state: countAnmeldungen } = useAsyncState(async () => {
const selectedAnmeldungId = ref()
const showDrawer = ref(false)
function toggleDrawer($event) {
selectedAnmeldungId.value = $event.id
showDrawer.value = true
getSingleAnmeldung()
}
const {
state: currentAnmeldung,
execute: getSingleAnmeldung,
Expand All @@ -128,6 +122,31 @@ const {
{ immediate: false }
)
const { state: customFields, execute: loadCustomFields } = useAsyncState(
async () => {
if (!currentAnmeldung.value) {
return []
}
return await apiClient.customFields.list.query({
entity: 'unterveranstaltung',
entityId: currentAnmeldung.value.unterveranstaltung.id,
})
},
[],
{
immediate: false,
}
)
async function toggleDrawer($event) {
selectedAnmeldungId.value = $event.id
showDrawer.value = true
await getSingleAnmeldung()
await loadCustomFields()
}
const { execute: update } = useAsyncState(
async (anmeldung: FormPersonGeneralSubmit) => {
const nahrungsmittelIntoleranzen = Object.entries(anmeldung.essgewohnheiten.intoleranzen)
Expand Down Expand Up @@ -269,10 +288,6 @@ const entityId = computed(() => {
return props.unterveranstaltungId || props.veranstaltungId
})
const entity = computed(() => {
return props.unterveranstaltungId ? 'unterveranstaltung' : 'veranstaltung'
})
const showNotification = ref(false)
</script>

Expand Down Expand Up @@ -460,12 +475,11 @@ const showNotification = ref(false)
/>
</Tab>
<Tab>
<CustomFieldsForm
<CustomFieldsFormUser
v-if="currentAnmeldung?.customFieldValues && entityId"
class="mt-8"
:entity="entity"
:entry-id="currentAnmeldung.id"
:entity-id="entityId"
:custom-fields="customFields"
:custom-field-values="currentAnmeldung?.customFieldValues"
@update:success="showNotification = true"
/>
Expand Down
110 changes: 110 additions & 0 deletions frontend/src/components/CustomFields/CustomFieldsFormCreate.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<script setup lang="ts">
import { useAsyncState } from '@vueuse/core'
import { ref } from 'vue'
import { apiClient } from '@/api'
import CustomFieldsFormGeneral, { type ICustomFieldData } from '@/components/CustomFields/CustomFieldsFormGeneral.vue'
import Button from '@/components/UIComponents/Button.vue'
import router from '@/router'
import { type CustomFieldType, type RouterInput } from '@codeanker/api'
import { ValidateForm } from '@codeanker/validation'
type Query = RouterInput['customFields']['list']
/* @vue-ignore */
const props = defineProps<{
entity: Query['entity']
entityId: Query['entityId']
}>()
const form = ref<ICustomFieldData>({
name: '',
description: '',
required: false,
type: 'BASIC_INPUT' as CustomFieldType,
options: [],
positions: [],
})
const validationErrors = ref([])
const { execute, error, isLoading } = useAsyncState(
async () => {
const data = form.value
try {
if (props.entity === 'veranstaltung') {
await apiClient.customFields.veranstaltungCreate.mutate({
veranstaltungId: props.entityId,
data: {
name: data.name,
description: data.description || null,
required: data.required,
type: data.type,
options: data.options || [],
positions: data.positions || [],
},
})
} else if (props.entity === 'unterveranstaltung') {
await apiClient.customFields.unterveranstaltungCreate.mutate({
unterveranstaltungId: props.entityId,
data: {
name: data.name,
description: data.description || null,
required: data.required,
type: data.type,
options: data.options || [],
positions: data.positions || [],
},
})
}
router.back()
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (error: any) {
if (error.shape) {
validationErrors.value = JSON.parse(error.shape?.message)
}
}
},
null,
{ immediate: false }
)
</script>

<template>
<h5>Benutzerdefiniertes Feld erstellen</h5>

<ValidateForm @submit="execute">
<CustomFieldsFormGeneral v-model="form" />

<div class="mt-4 flex gap-4 items-center">
<Button
type="submit"
color="primary"
>
<span v-if="!isLoading">Speichern</span>
<span v-else>Loading...</span>
</Button>
<Button
type="button"
color="warning"
@click="() => router.back()"
>
Abbrechen
</Button>
</div>
</ValidateForm>

<div class="mt-8">
<div v-if="validationErrors.length > 0">
<pre>{{ validationErrors }}</pre>
</div>
<div
v-else-if="error"
class="bg-danger-400 mb-2 mt-5 rounded p-3 text-white"
>
{{ error }}
</div>
</div>
</template>
Loading

0 comments on commit 0bde3c6

Please sign in to comment.