Skip to content

Commit 2116b80

Browse files
Merge pull request #461 from codeanker/feature/custom-field-ordering
feat: custom field ordering
2 parents 99f34a9 + 7a0779c commit 2116b80

14 files changed

Lines changed: 610 additions & 42 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- AlterTable
2+
ALTER TABLE "CustomField" ADD COLUMN "order" INTEGER;

apps/api/prisma/schema/CustomField.prisma

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ model CustomField {
1919
id String @id @default(uuid(7))
2020
name String
2121
description String?
22+
order Int?
2223
type CustomFieldType
2324
required Boolean @default(false)
2425
options String[]

apps/api/src/services/customFields/customFields.router.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { customFieldsVeranstaltungCreate } from './customFieldsVeranstaltungCrea
99
import { customFieldsVeranstaltungDelete, customFieldsUnterveranstaltungDelete } from './customFieldsDelete.js'
1010
import { customFieldsUpdate } from './customFieldsUpdate.js'
1111
import { customFieldValuesUpdate } from './customFieldValuesUpdate.js'
12+
import { customFieldUnterveranstaltungOrder } from './schema/customFieldsUnterveranstaltungOrder.js'
13+
import { customFieldVeranstaltungOrder } from './schema/customFieldsVeranstaltungOrder.js'
1214
// Import Routes here - do not delete this line
1315

1416
export const customFieldsRouter = mergeRouters(
@@ -21,6 +23,8 @@ export const customFieldsRouter = mergeRouters(
2123
customFieldsUnterveranstaltungDelete,
2224
customFieldsUnterveranstaltungCreate,
2325
customFieldValuesUpdate,
24-
customFieldsTemplates
26+
customFieldsTemplates,
27+
customFieldVeranstaltungOrder,
28+
customFieldUnterveranstaltungOrder
2529
// Add Routes here - do not delete this line
2630
)

apps/api/src/services/customFields/customFieldsList.ts

Lines changed: 48 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ import { z } from 'zod'
22

33
import { CustomFieldPosition, CustomFieldType, Prisma } from '@prisma/client'
44
import prisma from '../../prisma.js'
5-
import { definePublicQueryProcedure } from '../../types/defineProcedure.js'
5+
import { defineProtectedQueryProcedure, definePublicQueryProcedure } from '../../types/defineProcedure.js'
66
import {
77
calculatePagination,
88
defineEmptyQueryResponse,
99
defineQueryResponse,
1010
defineTableInput,
1111
} from '../../types/defineTableProcedure.js'
1212
import { boolish } from '../../util/zod.js'
13+
import { getGliederungRequireAdmin } from '../../util/getGliederungRequireAdmin.js'
1314

1415
const baseFilter = z.strictObject({
1516
entity: z.enum(['veranstaltung', 'unterveranstaltung']),
@@ -23,6 +24,7 @@ export const customFieldsTable = definePublicQueryProcedure({
2324
async handler({ input: { entity, entityId, position } }) {
2425
if (entity === 'veranstaltung') {
2526
return await prisma.customField.findMany({
27+
orderBy: [{ order: { sort: 'asc', nulls: 'last' } }],
2628
where: {
2729
veranstaltungId: entityId,
2830
positions:
@@ -35,6 +37,14 @@ export const customFieldsTable = definePublicQueryProcedure({
3537
})
3638
} else if (entity === 'unterveranstaltung') {
3739
return await prisma.customField.findMany({
40+
orderBy: [
41+
{
42+
unterveranstaltungId: { sort: 'asc', nulls: 'first' },
43+
},
44+
{
45+
order: { sort: 'asc', nulls: 'last' },
46+
},
47+
],
3848
where: {
3949
positions:
4050
position === undefined
@@ -64,8 +74,9 @@ export const customFieldsTable = definePublicQueryProcedure({
6474
},
6575
})
6676

67-
export const customFieldsList = definePublicQueryProcedure({
77+
export const customFieldsList = defineProtectedQueryProcedure({
6878
key: 'table',
79+
roleIds: ['ADMIN', 'GLIEDERUNG_ADMIN'],
6980
inputSchema: baseFilter.extend({
7081
table: defineTableInput({
7182
filter: {
@@ -74,10 +85,14 @@ export const customFieldsList = definePublicQueryProcedure({
7485
required: boolish,
7586
position: z.nativeEnum(CustomFieldPosition),
7687
},
77-
orderBy: ['name'],
88+
orderBy: ['name', 'order'],
7889
}),
7990
}),
80-
async handler({ input }) {
91+
async handler({ ctx, input }) {
92+
if (ctx.account.role === 'GLIEDERUNG_ADMIN') {
93+
await getGliederungRequireAdmin(ctx.accountId)
94+
}
95+
8196
const where: Prisma.CustomFieldWhereInput = {
8297
name: {
8398
contains: input.table?.filter?.name,
@@ -93,22 +108,39 @@ export const customFieldsList = definePublicQueryProcedure({
93108
},
94109
}
95110

111+
if (input.entity === 'veranstaltung') {
112+
where.veranstaltungId = input.entityId
113+
} else if (input.entity === 'unterveranstaltung') {
114+
where.OR = [
115+
{
116+
unterveranstaltungId: input.entityId,
117+
},
118+
{
119+
veranstaltung: {
120+
unterveranstaltungen: {
121+
some: {
122+
id: input.entityId,
123+
},
124+
},
125+
},
126+
},
127+
]
128+
}
129+
96130
const total = await prisma.customField.count({ where })
97131
const { pageIndex, pageSize, pages } = calculatePagination(total, input.table?.pagination)
98132

99133
if (input.entity === 'veranstaltung') {
100134
const customFields = await prisma.customField.findMany({
101135
take: pageSize,
102136
skip: pageSize * pageIndex,
103-
where: {
104-
...where,
105-
veranstaltungId: input.entityId,
106-
},
137+
where,
107138
orderBy: input.table?.orderBy,
108139
select: {
109140
id: true,
110141
name: true,
111142
description: true,
143+
order: true,
112144
type: true,
113145
positions: true,
114146
required: true,
@@ -122,27 +154,18 @@ export const customFieldsList = definePublicQueryProcedure({
122154
const customFields = await prisma.customField.findMany({
123155
take: pageSize,
124156
skip: pageSize * pageIndex,
125-
where: {
126-
...where,
127-
OR: [
128-
{
129-
unterveranstaltungId: input.entityId,
130-
},
131-
{
132-
veranstaltung: {
133-
unterveranstaltungen: {
134-
some: {
135-
id: input.entityId,
136-
},
137-
},
138-
},
139-
},
140-
],
141-
},
157+
orderBy: [
158+
{
159+
unterveranstaltungId: { sort: 'asc', nulls: 'first' },
160+
},
161+
...(input.table?.orderBy ?? []),
162+
],
163+
where,
142164
select: {
143165
id: true,
144166
name: true,
145167
description: true,
168+
order: true,
146169
type: true,
147170
positions: true,
148171
required: true,
Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
import { CustomFieldPosition, CustomFieldType } from '@prisma/client'
22
import { z } from 'zod'
33

4-
export const customFieldSchema = z.strictObject({
5-
name: z.string().min(1),
6-
description: z.string().nullable(),
7-
type: z.nativeEnum(CustomFieldType),
8-
required: z.boolean(),
9-
options: z.array(z.string()),
10-
positions: z.nativeEnum(CustomFieldPosition).array(),
11-
})
4+
export const customFieldSchema = z
5+
.strictObject({
6+
name: z.string().min(1),
7+
description: z.string().nullable(),
8+
type: z.nativeEnum(CustomFieldType),
9+
required: z.boolean(),
10+
options: z.array(z.string()),
11+
positions: z.nativeEnum(CustomFieldPosition).array().min(1),
12+
})
13+
.superRefine((values, ctx) => {
14+
const optionTypes = ['BASIC_DROPDOWN', 'BASIC_RADIO', 'BASIC_SWITCH'] as CustomFieldType[]
15+
if (optionTypes.includes(values.type) && values.options.length === 0) {
16+
ctx.addIssue({
17+
code: 'custom',
18+
message: 'Es muss mindestens eine Auswahlmöglichkeit angegeben werden',
19+
})
20+
}
21+
})
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import z from 'zod'
2+
import { defineProtectedMutateProcedure } from '../../../types/defineProcedure.js'
3+
import { getGliederungRequireAdmin } from '../../../util/getGliederungRequireAdmin.js'
4+
import { TRPCError } from '@trpc/server'
5+
import prisma from '../../../prisma.js'
6+
7+
export const customFieldUnterveranstaltungOrder = defineProtectedMutateProcedure({
8+
key: 'unterveranstaltungOrder',
9+
roleIds: ['ADMIN', 'GLIEDERUNG_ADMIN'],
10+
inputSchema: z.strictObject({
11+
unterveranstaltungId: z.string().uuid(),
12+
fields: z.array(z.string().uuid()),
13+
}),
14+
handler: async ({ ctx, input }) => {
15+
if (ctx.account.role === 'GLIEDERUNG_ADMIN') {
16+
const gliederung = await getGliederungRequireAdmin(ctx.accountId)
17+
if (gliederung.id !== input.unterveranstaltungId) {
18+
throw new TRPCError({
19+
code: 'FORBIDDEN',
20+
})
21+
}
22+
}
23+
24+
const fields = await prisma.customField.findMany({
25+
where: {
26+
unterveranstaltungId: input.unterveranstaltungId,
27+
id: {
28+
in: input.fields,
29+
},
30+
},
31+
})
32+
33+
if (fields.length !== input.fields.length) {
34+
throw new TRPCError({
35+
code: 'BAD_REQUEST',
36+
message: 'Some supplied fields do not belong to the given unterveranstaltung!',
37+
})
38+
}
39+
40+
await prisma.$transaction(
41+
input.fields.map((field, index) =>
42+
prisma.customField.update({
43+
where: {
44+
unterveranstaltungId: input.unterveranstaltungId,
45+
id: field,
46+
},
47+
data: {
48+
order: index + 1,
49+
},
50+
})
51+
)
52+
)
53+
},
54+
})
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { TRPCError } from '@trpc/server'
2+
import z from 'zod'
3+
import prisma from '../../../prisma.js'
4+
import { defineProtectedMutateProcedure } from '../../../types/defineProcedure.js'
5+
6+
export const customFieldVeranstaltungOrder = defineProtectedMutateProcedure({
7+
key: 'veranstaltungOrder',
8+
roleIds: ['ADMIN'],
9+
inputSchema: z.strictObject({
10+
veranstaltungId: z.string().uuid(),
11+
fields: z.array(z.string().uuid()),
12+
}),
13+
handler: async ({ input }) => {
14+
const fields = await prisma.customField.findMany({
15+
where: {
16+
veranstaltungId: input.veranstaltungId,
17+
id: {
18+
in: input.fields,
19+
},
20+
},
21+
})
22+
23+
if (fields.length !== input.fields.length) {
24+
throw new TRPCError({
25+
code: 'BAD_REQUEST',
26+
message: 'Some supplied fields do not belong to the given veranstaltung!',
27+
})
28+
}
29+
30+
await prisma.$transaction(
31+
input.fields.map((field, index) =>
32+
prisma.customField.update({
33+
where: {
34+
veranstaltungId: input.veranstaltungId,
35+
id: field,
36+
},
37+
data: {
38+
order: index + 1,
39+
},
40+
})
41+
)
42+
)
43+
},
44+
})

apps/frontend/src/components/CustomFields/CustomFieldsTable.vue

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,18 @@ const router = useRouter()
3232
3333
const column = createColumnHelper<CustomField>()
3434
const columns = [
35+
column.accessor('order', {
36+
header: 'Reihenfolge',
37+
enableSorting: true,
38+
cell({ getValue }) {
39+
const order = getValue()
40+
if (!order) {
41+
return '-'
42+
}
43+
44+
return order
45+
},
46+
}),
3547
column.accessor('name', {
3648
header: 'Name',
3749
enableColumnFilter: true,
@@ -174,6 +186,7 @@ function onClick(field: CustomField) {
174186
<DataTable
175187
:query="query"
176188
:columns="columns"
189+
:initial-sort="[{ id: 'order', desc: false }]"
177190
@click="onClick"
178191
/>
179192
</template>

0 commit comments

Comments
 (0)