Manually reorder event types (#1142)
* Add event type reordering * Add migration for position field * hack on a hack * can edit * fix ordering * Remove console.log Co-authored-by: Alex Johansson <alexander@n1s.se> Co-authored-by: KATT <alexander@n1s.se>
This commit is contained in:
parent
6fa980f801
commit
6b171a6f87
6 changed files with 184 additions and 7 deletions
|
@ -124,6 +124,14 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||
},
|
||||
],
|
||||
},
|
||||
orderBy: [
|
||||
{
|
||||
position: "desc",
|
||||
},
|
||||
{
|
||||
id: "asc",
|
||||
},
|
||||
],
|
||||
select: {
|
||||
id: true,
|
||||
slug: true,
|
||||
|
|
|
@ -1,13 +1,20 @@
|
|||
// TODO: replace headlessui with radix-ui
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
import { UsersIcon } from "@heroicons/react/solid";
|
||||
import { ChevronDownIcon, PlusIcon } from "@heroicons/react/solid";
|
||||
import { DotsHorizontalIcon, ExternalLinkIcon, LinkIcon } from "@heroicons/react/solid";
|
||||
import {
|
||||
DotsHorizontalIcon,
|
||||
ExternalLinkIcon,
|
||||
LinkIcon,
|
||||
ArrowDownIcon,
|
||||
ChevronDownIcon,
|
||||
PlusIcon,
|
||||
ArrowUpIcon,
|
||||
UsersIcon,
|
||||
} from "@heroicons/react/solid";
|
||||
import { SchedulingType } from "@prisma/client";
|
||||
import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { Fragment, useRef } from "react";
|
||||
import React, { Fragment, useRef, useState, useEffect } from "react";
|
||||
import { useMutation } from "react-query";
|
||||
|
||||
import { QueryCell } from "@lib/QueryCell";
|
||||
|
@ -72,10 +79,40 @@ interface EventTypeListProps {
|
|||
}
|
||||
const EventTypeList = ({ readOnly, types, profile }: EventTypeListProps): JSX.Element => {
|
||||
const { t } = useLocale();
|
||||
|
||||
const utils = trpc.useContext();
|
||||
const mutation = trpc.useMutation("viewer.eventTypeOrder", {
|
||||
onError: (err) => {
|
||||
console.error(err.message);
|
||||
},
|
||||
async onSettled() {
|
||||
await utils.cancelQuery(["viewer.eventTypes"]);
|
||||
await utils.invalidateQueries(["viewer.eventTypes"]);
|
||||
},
|
||||
});
|
||||
const [sortableTypes, setSortableTypes] = useState(types);
|
||||
useEffect(() => {
|
||||
setSortableTypes(types);
|
||||
}, [types]);
|
||||
function moveEventType(index: number, increment: 1 | -1) {
|
||||
const newList = [...sortableTypes];
|
||||
|
||||
const type = sortableTypes[index];
|
||||
const tmp = sortableTypes[index + increment];
|
||||
if (tmp) {
|
||||
newList[index] = tmp;
|
||||
newList[index + increment] = type;
|
||||
}
|
||||
setSortableTypes(newList);
|
||||
mutation.mutate({
|
||||
ids: newList.map((type) => type.id),
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mb-16 -mx-4 overflow-hidden bg-white border border-gray-200 rounded-sm sm:mx-0">
|
||||
<ul className="divide-y divide-neutral-200" data-testid="event-types">
|
||||
{types.map((type) => (
|
||||
{sortableTypes.map((type, index) => (
|
||||
<li
|
||||
key={type.id}
|
||||
className={classNames(
|
||||
|
@ -87,7 +124,17 @@ const EventTypeList = ({ readOnly, types, profile }: EventTypeListProps): JSX.El
|
|||
"hover:bg-neutral-50 flex justify-between items-center ",
|
||||
type.$disabled && "pointer-events-none"
|
||||
)}>
|
||||
<div className="flex items-center justify-between w-full px-4 py-4 sm:px-6 hover:bg-neutral-50">
|
||||
<div className="group flex items-center justify-between w-full px-4 py-4 sm:px-6 hover:bg-neutral-50">
|
||||
<button
|
||||
className="absolute mb-8 left-1/2 -ml-4 sm:ml-0 sm:left-[19px] border hover:border-transparent text-gray-400 transition-all hover:text-black hover:shadow group-hover:scale-100 scale-0 w-7 h-7 p-1 invisible group-hover:visible bg-white rounded-full"
|
||||
onClick={() => moveEventType(index, -1)}>
|
||||
<ArrowUpIcon />
|
||||
</button>
|
||||
<button
|
||||
className="absolute mt-8 left-1/2 -ml-4 sm:ml-0 sm:left-[19px] border hover:border-transparent text-gray-400 transition-all hover:text-black hover:shadow group-hover:scale-100 scale-0 w-7 h-7 p-1 invisible group-hover:visible bg-white rounded-full"
|
||||
onClick={() => moveEventType(index, 1)}>
|
||||
<ArrowDownIcon />
|
||||
</button>
|
||||
<Link href={"/event-types/" + type.id}>
|
||||
<a
|
||||
className="flex-grow text-sm truncate"
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "EventType" ADD COLUMN "position" INTEGER NOT NULL DEFAULT 0;
|
|
@ -21,6 +21,7 @@ model EventType {
|
|||
title String
|
||||
slug String
|
||||
description String?
|
||||
position Int @default(0)
|
||||
locations Json?
|
||||
length Int
|
||||
hidden Boolean @default(false)
|
||||
|
|
|
@ -517,6 +517,7 @@
|
|||
"confirm_delete_event_type": "Yes, delete event type",
|
||||
"integrations": "Integrations",
|
||||
"settings": "Settings",
|
||||
"event_type_moved_successfully": "Event type has been moved successfully",
|
||||
"next_step": "Skip step",
|
||||
"prev_step": "Prev step",
|
||||
"installed": "Installed",
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { BookingStatus, Prisma } from "@prisma/client";
|
||||
import _ from "lodash";
|
||||
import { z } from "zod";
|
||||
|
||||
import { checkPremiumUsername } from "@ee/lib/core/checkPremiumUsername";
|
||||
|
@ -85,6 +86,7 @@ const loggedInViewerRouter = createProtectedRouter()
|
|||
hidden: true,
|
||||
price: true,
|
||||
currency: true,
|
||||
position: true,
|
||||
users: {
|
||||
select: {
|
||||
id: true,
|
||||
|
@ -126,6 +128,14 @@ const loggedInViewerRouter = createProtectedRouter()
|
|||
},
|
||||
eventTypes: {
|
||||
select: eventTypeSelect,
|
||||
orderBy: [
|
||||
{
|
||||
position: "desc",
|
||||
},
|
||||
{
|
||||
id: "asc",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -136,6 +146,14 @@ const loggedInViewerRouter = createProtectedRouter()
|
|||
team: null,
|
||||
},
|
||||
select: eventTypeSelect,
|
||||
orderBy: [
|
||||
{
|
||||
position: "desc",
|
||||
},
|
||||
{
|
||||
id: "asc",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -150,6 +168,14 @@ const loggedInViewerRouter = createProtectedRouter()
|
|||
userId: ctx.user.id,
|
||||
},
|
||||
select: eventTypeSelect,
|
||||
orderBy: [
|
||||
{
|
||||
position: "desc",
|
||||
},
|
||||
{
|
||||
id: "asc",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
type EventTypeGroup = {
|
||||
|
@ -184,7 +210,7 @@ const loggedInViewerRouter = createProtectedRouter()
|
|||
name: user.name,
|
||||
image: user.avatar,
|
||||
},
|
||||
eventTypes: mergedEventTypes,
|
||||
eventTypes: _.orderBy(mergedEventTypes, ["position", "id"], ["desc", "asc"]),
|
||||
metadata: {
|
||||
membershipCount: 1,
|
||||
readOnly: false,
|
||||
|
@ -449,6 +475,98 @@ const loggedInViewerRouter = createProtectedRouter()
|
|||
data,
|
||||
});
|
||||
},
|
||||
})
|
||||
.mutation("eventTypeOrder", {
|
||||
input: z.object({
|
||||
ids: z.array(z.number()),
|
||||
}),
|
||||
async resolve({ input, ctx }) {
|
||||
const { prisma, user } = ctx;
|
||||
const allEventTypes = await ctx.prisma.eventType.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
where: {
|
||||
id: {
|
||||
in: input.ids,
|
||||
},
|
||||
OR: [
|
||||
{
|
||||
userId: user.id,
|
||||
},
|
||||
{
|
||||
users: {
|
||||
some: {
|
||||
id: user.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
team: {
|
||||
members: {
|
||||
some: {
|
||||
userId: user.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
const allEventTypeIds = new Set(allEventTypes.map((type) => type.id));
|
||||
if (input.ids.some((id) => !allEventTypeIds.has(id))) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
});
|
||||
}
|
||||
await Promise.all(
|
||||
_.reverse(input.ids).map((id, position) => {
|
||||
return prisma.eventType.update({
|
||||
where: {
|
||||
id,
|
||||
},
|
||||
data: {
|
||||
position,
|
||||
},
|
||||
});
|
||||
})
|
||||
);
|
||||
},
|
||||
})
|
||||
.mutation("eventTypePosition", {
|
||||
input: z.object({
|
||||
eventType: z.number(),
|
||||
action: z.string(),
|
||||
}),
|
||||
async resolve({ input, ctx }) {
|
||||
// This mutation is for the user to be able to order their event types by incrementing or decrementing the position number
|
||||
const { prisma } = ctx;
|
||||
if (input.eventType && input.action == "increment") {
|
||||
await prisma.eventType.update({
|
||||
where: {
|
||||
id: input.eventType,
|
||||
},
|
||||
data: {
|
||||
position: {
|
||||
increment: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (input.eventType && input.action == "decrement") {
|
||||
await prisma.eventType.update({
|
||||
where: {
|
||||
id: input.eventType,
|
||||
},
|
||||
data: {
|
||||
position: {
|
||||
decrement: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export const viewerRouter = createRouter()
|
||||
|
|
Loading…
Reference in a new issue