Skip to content

Commit

Permalink
feat: Clone a cocktail card (#243)
Browse files Browse the repository at this point in the history
Signed-off-by: Johannes Groß <mail@gross-johannes.de>
  • Loading branch information
jo-gross authored Apr 18, 2024
1 parent 6bc0d96 commit 2af1792
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 9 deletions.
47 changes: 46 additions & 1 deletion components/cards/CardOverviewItem.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { Role } from '@prisma/client';
import Link from 'next/link';
import { FaRegEdit } from 'react-icons/fa';
import { FaRegClone, FaRegEdit } from 'react-icons/fa';
import React, { useContext } from 'react';
import { UserContext } from '../../lib/context/UserContextProvider';
import { CocktailCardFull } from '../../models/CocktailCardFull';
import { ModalContext } from '../../lib/context/ModalContextProvider';
import InputModal from '../modals/InputModal';
import { alertService } from '../../lib/alertService';
import { useRouter } from 'next/router';

interface CardOverviewItemProps {
card: CocktailCardFull;
Expand All @@ -12,6 +16,9 @@ interface CardOverviewItemProps {

export default function CardOverviewItem(props: CardOverviewItemProps) {
const userContext = useContext(UserContext);
const modalContext = useContext(ModalContext);

const router = useRouter();

return (
<div key={'card-' + props.card.id} className={'card'}>
Expand All @@ -27,9 +34,47 @@ export default function CardOverviewItem(props: CardOverviewItemProps) {
<>
{userContext.isUserPermitted(Role.MANAGER) && (
<div className="card-actions justify-end">
<button
type={'button'}
className={'btn btn-outline btn-primary'}
onClick={() =>
modalContext.openModal(
<InputModal
title={'Name'}
onInputSubmit={async (value) => {
try {
const response = await fetch(`/api/workspaces/${props.workspaceId}/cards/${props.card.id}/clone`, {
method: 'POST',
body: JSON.stringify({ name: value }),
});

const body = await response.json();
if (response.ok) {
await router.replace(`/workspaces/${props.workspaceId}/manage/cards/${body.data.id}`);
alertService.success('Karte erfolgreich dupliziert');
} else {
console.error('CardId -> cloneCard', response);
alertService.error(body.message ?? 'Fehler beim Duplizieren der Karte', response.status, response.statusText);
}
} catch (error) {
console.error('CardId -> cloneCard', error);
alertService.error('Fehler beim Duplizieren der Karte');
throw error;
}
}}
allowEmpty={false}
defaultValue={props.card.name + ' - Kopie'}
/>,
)
}
>
<FaRegClone />
Duplizieren
</button>
<Link href={`/workspaces/${props.workspaceId}/manage/cards/${props.card.id}`}>
<div className="btn btn-primary">
<FaRegEdit />
Bearbeiten
</div>
</Link>
</div>
Expand Down
23 changes: 17 additions & 6 deletions components/modals/InputModal.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { ModalContext } from '../../lib/context/ModalContextProvider';
import { useContext, useState } from 'react';
import React, { useContext, useState } from 'react';

interface InputModalProps {
title: string;
description?: string;
defaultValue?: string;
onInputChange: (value: string) => void;
onInputSubmit: (value: string) => Promise<void>;
allowEmpty?: boolean;
}

Expand All @@ -14,6 +14,8 @@ export default function InputModal(props: InputModalProps) {

const [inputValue, setInputValue] = useState(props.defaultValue || '');

const [isSubmitting, setIsSubmitting] = useState(false);

return (
<div className={'flex w-full flex-col space-y-2'}>
<div className={'text-2xl font-bold'}>{props.title}</div>
Expand All @@ -27,13 +29,22 @@ export default function InputModal(props: InputModalProps) {
/>
<button
className={'btn btn-primary join-item'}
onClick={() => {
if (props.allowEmpty || inputValue.trim().length > 0) {
props.onInputChange(inputValue);
modalContext.closeModal();
disabled={isSubmitting || (!props.allowEmpty && inputValue.trim().length == 0)}
onClick={async () => {
try {
if (props.allowEmpty || inputValue.trim().length > 0) {
setIsSubmitting(true);
await props.onInputSubmit(inputValue);
modalContext.closeModal();
}
} catch (error) {
console.error('InputModal -> onInputSubmit', error);
} finally {
setIsSubmitting(false);
}
}}
>
{isSubmitting ? <span className={'loading loading-spinner'} /> : <></>}
Speichern
</button>
</div>
Expand Down
72 changes: 72 additions & 0 deletions pages/api/workspaces/[workspaceId]/cards/[cardId]/clone.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { withHttpMethods } from '../../../../../../middleware/api/handleMethods';
import HTTPMethod from 'http-method-enum';
import { withWorkspacePermission } from '../../../../../../middleware/api/authenticationMiddleware';
import { Role } from '@prisma/client';
import { NextApiRequest, NextApiResponse } from 'next';
import prisma from '../../../../../../lib/prisma';

export default withHttpMethods({
[HTTPMethod.POST]: withWorkspacePermission([Role.MANAGER], async (req: NextApiRequest, res: NextApiResponse, user, workspace) => {
const cardId = req.query.cardId as string | undefined;
if (!cardId) return res.status(400).json({ message: 'No card id' });
const { name } = JSON.parse(req.body);

return prisma.$transaction(async (transaction) => {
const existing = await prisma.cocktailCard.findFirst({
where: {
id: cardId,
workspaceId: workspace.id,
},
});
if (!existing) return res.status(404).json({ message: 'Card not found' });

const createClone = await transaction.cocktailCard.create({
data: {
id: undefined,
name: name,
createdAt: undefined,
updatedAt: undefined,
workspaceId: workspace.id,
showTime: existing.showTime,
date: existing.date,
archived: existing.archived,
},
});

const groups = await prisma.cocktailCardGroup.findMany({
where: {
cocktailCardId: existing.id,
},
include: {
items: true,
},
});

for (const group of groups) {
const createGroup = await transaction.cocktailCardGroup.create({
data: {
id: undefined,
name: group.name,
groupNumber: group.groupNumber,
groupPrice: group.groupPrice,
items: {},
cocktailCard: { connect: { id: createClone.id } },
},
});

for (const item of group.items) {
await transaction.cocktailCardGroupItem.create({
data: {
itemNumber: item.itemNumber,
specialPrice: item.specialPrice,
cocktailCardGroup: { connect: { id: createGroup.id } },
cocktail: { connect: { id: item.cocktailId } },
},
});
}
}

return res.json({ data: createClone });
});
}),
});
4 changes: 3 additions & 1 deletion pages/workspaces/[workspaceId]/manage/calculations/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,9 @@ export default function CalculationPage() {
}, [calculationName, id, saveCalculationBackend]);

const openNameModal = useCallback(() => {
modalContext.openModal(<InputModal title={'Kalkulation speichern'} onInputChange={(value) => setCalculationName(value)} defaultValue={calculationName} />);
modalContext.openModal(
<InputModal title={'Kalkulation speichern'} onInputSubmit={async (value) => setCalculationName(value)} defaultValue={calculationName} />,
);
}, [calculationName, modalContext]);

// All must have the same ingredient
Expand Down
2 changes: 1 addition & 1 deletion pages/workspaces/[workspaceId]/manage/cards/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -531,7 +531,7 @@ function EditCocktailCard() {
modalContext.openModal(
<DeleteConfirmationModal
spelling={'DELETE'}
entityName={'die Karte'}
entityName={`'${card.name}'`}
onApprove={async () => {
if (!workspaceId) return;
const response = await fetch(`/api/workspaces/${workspaceId}/cards/${card.id}`, {
Expand Down

0 comments on commit 2af1792

Please sign in to comment.