From bb0f61b51ebcfd421ba493861ec3b54074617cce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Gro=C3=9F?= Date: Fri, 13 Dec 2024 16:54:51 +0100 Subject: [PATCH 01/10] feat: Added basic queue notes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Johannes Groß --- .idea/git_toolbox_blame.xml | 6 ++ components/StatisticActions.tsx | 74 ++++++++++++++++ .../cocktails/CocktailRecipeCardItem.tsx | 47 ++--------- .../cocktails/ShowCocktailInfoButton.tsx | 5 +- components/modals/AddCocktailToQueueModal.tsx | 84 +++++++++++++++++++ components/modals/CocktailDetailModal.tsx | 75 ++++++++--------- components/modals/SearchModal.tsx | 39 +-------- lib/network/cocktailTracking.ts | 24 ++++-- .../api/workspaces/[workspaceId]/queue/add.ts | 17 ++-- .../workspaces/[workspaceId]/queue/index.tsx | 1 + .../workspaces/[workspaceId]/queue/remove.ts | 3 +- .../[workspaceId]/statistics/cocktails/add.ts | 3 +- pages/workspaces/[workspaceId]/index.tsx | 34 +++++--- prisma/schema.prisma | 1 + 14 files changed, 276 insertions(+), 137 deletions(-) create mode 100644 .idea/git_toolbox_blame.xml create mode 100644 components/StatisticActions.tsx create mode 100644 components/modals/AddCocktailToQueueModal.tsx diff --git a/.idea/git_toolbox_blame.xml b/.idea/git_toolbox_blame.xml new file mode 100644 index 00000000..7dc12496 --- /dev/null +++ b/.idea/git_toolbox_blame.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/components/StatisticActions.tsx b/components/StatisticActions.tsx new file mode 100644 index 00000000..304a78c1 --- /dev/null +++ b/components/StatisticActions.tsx @@ -0,0 +1,74 @@ +import { addCocktailToQueue, addCocktailToStatistic } from '../lib/network/cocktailTracking'; +import { MdPlaylistAdd } from 'react-icons/md'; +import { FaCheck } from 'react-icons/fa'; +import React, { useContext, useState } from 'react'; +import { ModalContext } from '../lib/context/ModalContextProvider'; +import AddCocktailToQueueModal from './modals/AddCocktailToQueueModal'; + +interface StatisticActionsProps { + workspaceId: string; + cocktailId: string; + + cardId?: string; + actionSource: 'SEARCH_MODAL' | 'CARD' | 'DETAIL_MODAL' | 'QUEUE'; + notes?: string; + + onAddToQueue?: () => void; + onMarkedAsDone?: () => void; +} + +export default function StatisticActions({ workspaceId, cocktailId, cardId, actionSource, notes, onMarkedAsDone, onAddToQueue }: StatisticActionsProps) { + const [submittingQueue, setSubmittingQueue] = useState(false); + const [submittingStatistic, setSubmittingStatistic] = useState(false); + + const modalContext = useContext(ModalContext); + + return ( +
+ + + + +
+ ); +} diff --git a/components/cocktails/CocktailRecipeCardItem.tsx b/components/cocktails/CocktailRecipeCardItem.tsx index 122a5e49..0a2a27e8 100644 --- a/components/cocktails/CocktailRecipeCardItem.tsx +++ b/components/cocktails/CocktailRecipeCardItem.tsx @@ -3,13 +3,12 @@ import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useStat import { CompactCocktailRecipeInstruction } from './CompactCocktailRecipeInstruction'; import { ShowCocktailInfoButton } from './ShowCocktailInfoButton'; import { useRouter } from 'next/router'; -import { FaExclamationTriangle, FaPlus } from 'react-icons/fa'; -import { MdPlaylistAdd } from 'react-icons/md'; -import { addCocktailToQueue, addCocktailToStatistic } from '../../lib/network/cocktailTracking'; +import { FaExclamationTriangle } from 'react-icons/fa'; import { Loading } from '../Loading'; import { fetchCocktail } from '../../lib/network/cocktails'; import { CocktailRating } from '@prisma/client'; import { fetchCocktailRatings } from '../../lib/network/cocktailRatings'; +import StatisticActions from '../StatisticActions'; export type CocktailRecipeOverviewItemRef = { refresh: () => void; @@ -120,41 +119,13 @@ const CocktailRecipeCardItem = forwardRef -
- - -
+
+
) : ( <> diff --git a/components/cocktails/ShowCocktailInfoButton.tsx b/components/cocktails/ShowCocktailInfoButton.tsx index b143286a..8a79255a 100644 --- a/components/cocktails/ShowCocktailInfoButton.tsx +++ b/components/cocktails/ShowCocktailInfoButton.tsx @@ -16,7 +16,10 @@ export function ShowCocktailInfoButton(props: ShowCocktailInfoButtonProps) {
{ - modalContext.openModal( props.onRatingChange()} />, true); + modalContext.openModal( + props.onRatingChange()} openReferer={'DETAIL'} />, + true, + ); }} > diff --git a/components/modals/AddCocktailToQueueModal.tsx b/components/modals/AddCocktailToQueueModal.tsx new file mode 100644 index 00000000..aada3827 --- /dev/null +++ b/components/modals/AddCocktailToQueueModal.tsx @@ -0,0 +1,84 @@ +import { addCocktailToQueue } from '../../lib/network/cocktailTracking'; +import { MdPlaylistAdd } from 'react-icons/md'; +import React, { useContext, useState } from 'react'; +import { ModalContext } from '../../lib/context/ModalContextProvider'; +import { FaMinus, FaPlus } from 'react-icons/fa'; + +interface AddCocktailToQueueModalProps { + workspaceId: string; + cocktailId: string; + actionSource: 'SEARCH_MODAL' | 'CARD' | 'DETAIL_MODAL' | 'QUEUE'; +} + +export default function AddCocktailToQueueModal({ workspaceId, cocktailId, actionSource }: AddCocktailToQueueModalProps) { + const [submittingQueue, setSubmittingQueue] = useState(false); + + const [notes, setNotes] = useState(''); + const [amount, setAmount] = useState(1); + + const modalContext = useContext(ModalContext); + return ( +
+
+ {'Tommys Marairat mi'} hinzufügen +
+
+
+ + + +
+
+ + +
+ ); +} diff --git a/components/modals/CocktailDetailModal.tsx b/components/modals/CocktailDetailModal.tsx index a51637b7..3db3c01c 100644 --- a/components/modals/CocktailDetailModal.tsx +++ b/components/modals/CocktailDetailModal.tsx @@ -9,8 +9,6 @@ import { CocktailRating, Role } from '@prisma/client'; import Image from 'next/image'; import AvatarImage from '../AvatarImage'; import { Loading } from '../Loading'; -import { MdPlaylistAdd } from 'react-icons/md'; -import { addCocktailToQueue, addCocktailToStatistic } from '../../lib/network/cocktailTracking'; import ImageModal from './ImageModal'; import { calcCocktailTotalPrice } from '../../lib/CocktailRecipeCalculation'; import { fetchIngredients } from '../../lib/network/ingredients'; @@ -20,11 +18,15 @@ import CocktailRatingsModal from './CocktailRatingsModal'; import StarsComponent from '../StarsComponent'; import { fetchCocktailRatings } from '../../lib/network/cocktailRatings'; import { fetchCocktail } from '../../lib/network/cocktails'; +import StatisticActions from '../StatisticActions'; interface CocktailDetailModalProps { cocktailId: string; onRefreshRatings: () => void; + queueNotes?: string; + queueAmount?: number; + openReferer: 'QUEUE' | 'DETAIL'; } export function CocktailDetailModal(props: CocktailDetailModalProps) { @@ -42,6 +44,8 @@ export function CocktailDetailModal(props: CocktailDetailModalProps) { const [ratingsLoading, setRatingsLoading] = useState(true); const [ratingError, setRatingsError] = useState(false); + const [localQueueAmount, setLocalQueueAmount] = useState(props.queueAmount); + const refreshRatings = useCallback(() => { props.onRefreshRatings(); fetchCocktailRatings(workspaceId, props.cocktailId, setCocktailRatings, setRatingsLoading, setRatingsError); @@ -53,9 +57,6 @@ export function CocktailDetailModal(props: CocktailDetailModalProps) { fetchCocktailRatings(workspaceId, props.cocktailId, setCocktailRatings, setRatingsLoading, setRatingsError); }, [props.cocktailId, workspaceId]); - const [submittingStatistic, setSubmittingStatistic] = useState(false); - const [submittingQueue, setSubmittingQueue] = useState(false); - return loading || loadedCocktail == undefined ? ( ) : ( @@ -92,6 +93,20 @@ export function CocktailDetailModal(props: CocktailDetailModalProps) {
+ {(props.queueNotes || localQueueAmount) && ( +
+ {localQueueAmount && ( +
+ Anzahl: {localQueueAmount}x +
+ )} + {props.queueNotes && ( +
+ Warteschlangennotiz: {props.queueNotes} +
+ )} +
+ )}
{/*Left side*/}
@@ -218,39 +233,23 @@ export function CocktailDetailModal(props: CocktailDetailModalProps) { )} -
- - -
+ { + if (localQueueAmount === 1) { + modalContext.closeAllModals(); + } else { + setLocalQueueAmount((prev) => (prev ?? 0) - 1); + } + } + : undefined + } + />
{/*Right side*/}
diff --git a/components/modals/SearchModal.tsx b/components/modals/SearchModal.tsx index 5f599dda..d29ddabd 100644 --- a/components/modals/SearchModal.tsx +++ b/components/modals/SearchModal.tsx @@ -5,11 +5,9 @@ import { Loading } from '../Loading'; import { ModalContext } from '../../lib/context/ModalContextProvider'; import { useRouter } from 'next/router'; import { alertService } from '../../lib/alertService'; -import { FaPlus } from 'react-icons/fa'; -import { MdPlaylistAdd } from 'react-icons/md'; -import { addCocktailToQueue, addCocktailToStatistic } from '../../lib/network/cocktailTracking'; import CocktailRecipeCardItem from '../cocktails/CocktailRecipeCardItem'; import _ from 'lodash'; +import StatisticActions from '../StatisticActions'; interface SearchModalProps { onCocktailSelectedObject?: (cocktail: CocktailRecipeFull) => void; @@ -137,39 +135,8 @@ export function SearchModal(props: SearchModalProps) { )} {props.showStatisticActions ? ( -
- - - +
+
) : ( <> diff --git a/lib/network/cocktailTracking.ts b/lib/network/cocktailTracking.ts index dc616097..77864d77 100644 --- a/lib/network/cocktailTracking.ts +++ b/lib/network/cocktailTracking.ts @@ -5,15 +5,17 @@ export async function addCocktailToStatistic({ cocktailId, cardId, actionSource, + notes, setSubmitting, - reload, + onSuccess, }: { workspaceId: string; cocktailId: string; cardId?: string | string[] | undefined; actionSource: 'SEARCH_MODAL' | 'CARD' | 'DETAIL_MODAL' | 'QUEUE'; + notes?: string; setSubmitting: (submitting: boolean) => void; - reload?: () => void; + onSuccess?: () => void; }) { try { setSubmitting(true); @@ -26,10 +28,11 @@ export async function addCocktailToStatistic({ cocktailId: cocktailId, cocktailCardId: cardId, actionSource: actionSource, + notes: notes, }), }); if (response.ok) { - reload?.(); + onSuccess?.(); alertService.success('Cocktail als gemacht markiert'); } else { const body = await response.json(); @@ -47,13 +50,17 @@ export async function addCocktailToStatistic({ export async function addCocktailToQueue({ workspaceId, cocktailId, + notes, + amount, setSubmitting, - reload, + onSuccess, }: { workspaceId: string; cocktailId: string; + notes?: string; + amount?: number; setSubmitting: (submitting: boolean) => void; - reload?: () => void; + onSuccess?: () => void; }) { try { setSubmitting(true); @@ -64,11 +71,13 @@ export async function addCocktailToQueue({ }, body: JSON.stringify({ cocktailId: cocktailId, + notes: notes, + amount: amount, }), }); if (response.ok) { // alertService.success('Cocktail zur Warteschlange hinzugefügt'); - reload?.(); + onSuccess?.(); alertService.info('Cocktail zur Warteschlange hinzugefügt'); } else { const body = await response.json(); @@ -86,11 +95,13 @@ export async function addCocktailToQueue({ export async function removeCocktailFromQueue({ workspaceId, cocktailId, + notes: notes, setSubmitting, reload, }: { workspaceId: string; cocktailId: string; + notes?: string; setSubmitting: (submitting: boolean) => void; reload?: () => void; }) { @@ -103,6 +114,7 @@ export async function removeCocktailFromQueue({ }, body: JSON.stringify({ cocktailId: cocktailId, + notes: notes, }), }); if (response.ok) { diff --git a/pages/api/workspaces/[workspaceId]/queue/add.ts b/pages/api/workspaces/[workspaceId]/queue/add.ts index 3edf9a68..c759b1fd 100644 --- a/pages/api/workspaces/[workspaceId]/queue/add.ts +++ b/pages/api/workspaces/[workspaceId]/queue/add.ts @@ -8,7 +8,7 @@ import CocktailQueueCreateInput = Prisma.CocktailQueueCreateInput; export default withHttpMethods({ [HTTPMethod.POST]: withWorkspacePermission([Role.USER], async (req: NextApiRequest, res: NextApiResponse, user, workspace) => { - const { cocktailId } = req.body; + const { cocktailId, notes, amount } = req.body; const input: CocktailQueueCreateInput = { workspace: { @@ -21,10 +21,17 @@ export default withHttpMethods({ id: cocktailId, }, }, + notes: notes ? (notes.trim() == '' ? undefined : notes.trim()) : undefined, }; - const result = await prisma.cocktailQueue.create({ - data: input, - }); - return res.json({ data: result }); + + const results = []; + for (let i = 0; i < (amount ?? 1); i++) { + const result = await prisma.cocktailQueue.create({ + data: input, + }); + results.push(result); + } + + return res.json({ data: results }); }), }); diff --git a/pages/api/workspaces/[workspaceId]/queue/index.tsx b/pages/api/workspaces/[workspaceId]/queue/index.tsx index 73dad0d9..8bed4efa 100644 --- a/pages/api/workspaces/[workspaceId]/queue/index.tsx +++ b/pages/api/workspaces/[workspaceId]/queue/index.tsx @@ -47,6 +47,7 @@ export default withHttpMethods({ cocktailId: cocktailQueueItem.cocktailId, timestamp: cocktailQueueItem.createdAt, cocktailName: cocktailQueueItem.cocktail.name, + notes: cocktailQueueItem.notes, }; }), }); diff --git a/pages/api/workspaces/[workspaceId]/queue/remove.ts b/pages/api/workspaces/[workspaceId]/queue/remove.ts index 019587b6..39c91636 100644 --- a/pages/api/workspaces/[workspaceId]/queue/remove.ts +++ b/pages/api/workspaces/[workspaceId]/queue/remove.ts @@ -7,12 +7,13 @@ import { withHttpMethods } from '../../../../../middleware/api/handleMethods'; export default withHttpMethods({ [HTTPMethod.POST]: withWorkspacePermission([Role.USER], async (req: NextApiRequest, res: NextApiResponse, user, workspace) => { - const { cocktailId } = req.body; + const { cocktailId, notes } = req.body; const firstQueueItem = await prisma.cocktailQueue.findFirst({ where: { workspaceId: workspace.id, cocktailId: cocktailId, + notes: notes, }, orderBy: { createdAt: 'asc', diff --git a/pages/api/workspaces/[workspaceId]/statistics/cocktails/add.ts b/pages/api/workspaces/[workspaceId]/statistics/cocktails/add.ts index 609b09b6..bbb069fa 100644 --- a/pages/api/workspaces/[workspaceId]/statistics/cocktails/add.ts +++ b/pages/api/workspaces/[workspaceId]/statistics/cocktails/add.ts @@ -8,7 +8,7 @@ import CocktailStatisticItemCreateInput = Prisma.CocktailStatisticItemCreateInpu export default withHttpMethods({ [HTTPMethod.POST]: withWorkspacePermission([Role.USER], async (req: NextApiRequest, res: NextApiResponse, user, workspace) => { - const { cocktailId, cocktailCardId, actionSource } = req.body; + const { cocktailId, cocktailCardId, actionSource, notes } = req.body; var cardId = cocktailCardId as string | undefined; if (cardId == 'search') { @@ -45,6 +45,7 @@ export default withHttpMethods({ where: { workspaceId: workspace.id, cocktailId: cocktailId, + notes: notes, }, }); if (queueItem) { diff --git a/pages/workspaces/[workspaceId]/index.tsx b/pages/workspaces/[workspaceId]/index.tsx index 8359aeee..fa627ec9 100644 --- a/pages/workspaces/[workspaceId]/index.tsx +++ b/pages/workspaces/[workspaceId]/index.tsx @@ -213,6 +213,7 @@ export default function OverviewPage() { cocktailId: string; timestamp: Date; cocktailName: string; + notes?: string; } const [cocktailQueue, setCocktailQueue] = useState([]); @@ -336,23 +337,29 @@ export default function OverviewPage() {
Warteschlange (A-Z)
{_(cocktailQueue) - .groupBy('cocktailId') // Gruppiere nach Cocktail-ID - .map((items, cocktailId) => ({ - cocktailId, - cocktailName: items[0].cocktailName, - count: items.length, - oldestTimestamp: _.minBy(items, 'timestamp')!.timestamp, - })) - .sortBy('cocktailName') + // .groupBy('cocktailId') + .groupBy((item) => `${item.cocktailId}||${item.notes}`) // Gruppierung basierend auf cocktailId und notes + .map((items, key) => { + const [cocktailId, notes] = key.split('||'); // Extrahiere cocktailId und notes aus dem Key + return { + cocktailId, + notes: notes === 'null' || notes === '' ? undefined : notes, + cocktailName: items[0].cocktailName, + count: items.length, + oldestTimestamp: _.minBy(items, 'timestamp')!.timestamp, // Finde den ältesten Timestamp + }; + }) + .sortBy(['cocktailName', (item) => -(item.notes ?? '')]) // Sortiere nach cocktailName (asc) und notes (desc) .value() .map((cocktailQueueItem, index) => (
- {cocktailQueueItem.count}x {cocktailQueueItem.cocktailName} (seit{' '} - {new Date(cocktailQueueItem.oldestTimestamp).toFormatTimeString()} Uhr) + {cocktailQueueItem.count}x {cocktailQueueItem.cocktailName} {cocktailQueueItem.notes && mit Notiz} + (seit {new Date(cocktailQueueItem.oldestTimestamp).toFormatTimeString()} Uhr)
+ {cocktailQueueItem.notes &&
{cocktailQueueItem.notes}
}
handleCocktailCardRefresh(cocktailQueueItem.cocktailName)} + queueNotes={cocktailQueueItem.notes} + queueAmount={cocktailQueueItem.count} + openReferer={'QUEUE'} />, true, ) @@ -377,6 +387,7 @@ export default function OverviewPage() { workspaceId: router.query.workspaceId as string, cocktailId: cocktailQueueItem.cocktailId, actionSource: 'QUEUE', + notes: cocktailQueueItem.notes, setSubmitting: (submitting) => { if (submitting) { setSubmittingQueue([...submittingQueue, { cocktailId: cocktailQueueItem.cocktailId, mode: 'ACCEPT' }]); @@ -384,7 +395,7 @@ export default function OverviewPage() { setSubmittingQueue(submittingQueue.filter((i) => i.cocktailId != cocktailQueueItem.cocktailId)); } }, - reload: () => { + onSuccess: () => { refreshQueue(); }, }) @@ -399,6 +410,7 @@ export default function OverviewPage() { removeCocktailFromQueue({ workspaceId: router.query.workspaceId as string, cocktailId: cocktailQueueItem.cocktailId, + notes: cocktailQueueItem.notes, setSubmitting: (submitting) => { if (submitting) { setSubmittingQueue([...submittingQueue, { cocktailId: cocktailQueueItem.cocktailId, mode: 'REJECT' }]); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 587b5eae..c045d318 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -475,6 +475,7 @@ model CocktailQueue { cocktail CocktailRecipe @relation(fields: [cocktailId], references: [id], onDelete: Cascade) workspaceId String workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade) + notes String? createdAt DateTime @default(now()) updatedAt DateTime @default(now()) @updatedAt } From 28ca85ffb76b67940cb65236e58d34d4e11585b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Gro=C3=9F?= Date: Fri, 13 Dec 2024 18:39:00 +0100 Subject: [PATCH 02/10] feat: Added basic queue notes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Johannes Groß --- components/StatisticActions.tsx | 25 +++++- .../cocktails/CocktailRecipeCardItem.tsx | 1 + components/modals/AddCocktailToQueueModal.tsx | 6 +- components/modals/CocktailDetailModal.tsx | 1 + components/modals/SearchModal.tsx | 7 +- ...SelectSpecifyCocktailForStatisticModal.tsx | 88 +++++++++++++++++++ lib/network/cocktailTracking.ts | 14 ++- models/StatisticBadRequest.ts | 1 + .../[workspaceId]/statistics/cocktails/add.ts | 78 +++++++++++++--- pages/workspaces/[workspaceId]/index.tsx | 13 +-- .../migration.sql | 3 + 11 files changed, 215 insertions(+), 22 deletions(-) create mode 100644 components/modals/SelectSpecifyCocktailForStatisticModal.tsx create mode 100644 models/StatisticBadRequest.ts create mode 100644 prisma/migrations/20241213173603_add_queue_notes/migration.sql diff --git a/components/StatisticActions.tsx b/components/StatisticActions.tsx index 304a78c1..a23d2182 100644 --- a/components/StatisticActions.tsx +++ b/components/StatisticActions.tsx @@ -4,10 +4,12 @@ import { FaCheck } from 'react-icons/fa'; import React, { useContext, useState } from 'react'; import { ModalContext } from '../lib/context/ModalContextProvider'; import AddCocktailToQueueModal from './modals/AddCocktailToQueueModal'; +import SelectSpecifyCocktailForStatisticModal from './modals/SelectSpecifyCocktailForStatisticModal'; interface StatisticActionsProps { workspaceId: string; cocktailId: string; + cocktailName: string; cardId?: string; actionSource: 'SEARCH_MODAL' | 'CARD' | 'DETAIL_MODAL' | 'QUEUE'; @@ -17,7 +19,16 @@ interface StatisticActionsProps { onMarkedAsDone?: () => void; } -export default function StatisticActions({ workspaceId, cocktailId, cardId, actionSource, notes, onMarkedAsDone, onAddToQueue }: StatisticActionsProps) { +export default function StatisticActions({ + workspaceId, + cocktailId, + cardId, + actionSource, + notes, + onMarkedAsDone, + onAddToQueue, + cocktailName, +}: StatisticActionsProps) { const [submittingQueue, setSubmittingQueue] = useState(false); const [submittingStatistic, setSubmittingStatistic] = useState(false); @@ -61,6 +72,18 @@ export default function StatisticActions({ workspaceId, cocktailId, cardId, acti notes: notes, setSubmitting: setSubmittingStatistic, onSuccess: () => onMarkedAsDone?.(), + onNotDecidableError: (options) => { + modalContext.openModal( + , + ); + }, }) } disabled={submittingStatistic} diff --git a/components/cocktails/CocktailRecipeCardItem.tsx b/components/cocktails/CocktailRecipeCardItem.tsx index 0a2a27e8..10587041 100644 --- a/components/cocktails/CocktailRecipeCardItem.tsx +++ b/components/cocktails/CocktailRecipeCardItem.tsx @@ -123,6 +123,7 @@ const CocktailRecipeCardItem = forwardRef diff --git a/components/modals/AddCocktailToQueueModal.tsx b/components/modals/AddCocktailToQueueModal.tsx index aada3827..edd09c29 100644 --- a/components/modals/AddCocktailToQueueModal.tsx +++ b/components/modals/AddCocktailToQueueModal.tsx @@ -37,7 +37,7 @@ export default function AddCocktailToQueueModal({ workspaceId, cocktailId, actio />
) : ( <> diff --git a/components/modals/SelectSpecifyCocktailForStatisticModal.tsx b/components/modals/SelectSpecifyCocktailForStatisticModal.tsx new file mode 100644 index 00000000..36951b3a --- /dev/null +++ b/components/modals/SelectSpecifyCocktailForStatisticModal.tsx @@ -0,0 +1,88 @@ +import { addCocktailToStatistic } from '../../lib/network/cocktailTracking'; +import { FaCheck } from 'react-icons/fa'; +import React, { useContext } from 'react'; +import { ModalContext } from '../../lib/context/ModalContextProvider'; + +interface SelectSpecifyCocktailForStatisticModalProps { + workspaceId: string; + cocktailId: string; + cardId?: string; + cocktailName: string; + actionSource: 'SEARCH_MODAL' | 'CARD' | 'DETAIL_MODAL' | 'QUEUE'; + options: { _min: { id: string }; notes: string }[]; +} + +export default function SelectSpecifyCocktailForStatisticModal({ + workspaceId, + cocktailId, + cardId, + cocktailName, + options, + actionSource, +}: SelectSpecifyCocktailForStatisticModalProps) { + const modalContext = useContext(ModalContext); + const [submittingStatistic, setSubmittingStatistic] = React.useState<{ [key: string]: boolean }>({}); + + return ( +
+
Warteschlange - {cocktailName}
+
In der Warteschlange befinden sich Einträge mit Notizen, war es einer davon?
+
+ {options.map((option) => ( +
+
+
Notiz:
+
{option.notes}
+
+ +
+ ))} + +
+ Keiner davon, normale Variante. + +
+
+
+ ); +} diff --git a/lib/network/cocktailTracking.ts b/lib/network/cocktailTracking.ts index 77864d77..8a5e2ba1 100644 --- a/lib/network/cocktailTracking.ts +++ b/lib/network/cocktailTracking.ts @@ -1,4 +1,5 @@ import { alertService } from '../alertService'; +import { StatisticBadRequestMessage } from '../../models/StatisticBadRequest'; export async function addCocktailToStatistic({ workspaceId, @@ -7,15 +8,19 @@ export async function addCocktailToStatistic({ actionSource, notes, setSubmitting, + ignoreQueue, onSuccess, + onNotDecidableError, }: { workspaceId: string; cocktailId: string; cardId?: string | string[] | undefined; actionSource: 'SEARCH_MODAL' | 'CARD' | 'DETAIL_MODAL' | 'QUEUE'; notes?: string; + ignoreQueue?: boolean; setSubmitting: (submitting: boolean) => void; onSuccess?: () => void; + onNotDecidableError?: (data: { _min: { id: string; createdAt: Date }; cocktailId: string; notes: string }[]) => void; }) { try { setSubmitting(true); @@ -29,6 +34,7 @@ export async function addCocktailToStatistic({ cocktailCardId: cardId, actionSource: actionSource, notes: notes, + ignoreQueue: ignoreQueue, }), }); if (response.ok) { @@ -36,8 +42,12 @@ export async function addCocktailToStatistic({ alertService.success('Cocktail als gemacht markiert'); } else { const body = await response.json(); - console.error('addCocktailToStatistic', response); - alertService.error(body.message ?? 'Fehler beim Hinzufügen des Cocktails zur Statistik', response.status, response.statusText); + if (body.message == StatisticBadRequestMessage) { + onNotDecidableError?.(body.data); + } else { + console.error('addCocktailToStatistic', response); + alertService.error(body.message ?? 'Fehler beim Hinzufügen des Cocktails zur Statistik', response.status, response.statusText); + } } } catch (error) { console.error('addCocktailToStatistic', error); diff --git a/models/StatisticBadRequest.ts b/models/StatisticBadRequest.ts new file mode 100644 index 00000000..28bccac7 --- /dev/null +++ b/models/StatisticBadRequest.ts @@ -0,0 +1 @@ +export const StatisticBadRequestMessage = 'Cocktail not specified. Please specify.'; diff --git a/pages/api/workspaces/[workspaceId]/statistics/cocktails/add.ts b/pages/api/workspaces/[workspaceId]/statistics/cocktails/add.ts index bbb069fa..25b696ec 100644 --- a/pages/api/workspaces/[workspaceId]/statistics/cocktails/add.ts +++ b/pages/api/workspaces/[workspaceId]/statistics/cocktails/add.ts @@ -4,11 +4,12 @@ import { Prisma, Role } from '@prisma/client'; import { NextApiRequest, NextApiResponse } from 'next'; import prisma from '../../../../../../prisma/prisma'; import { withHttpMethods } from '../../../../../../middleware/api/handleMethods'; +import { StatisticBadRequestMessage } from '../../../../../../models/StatisticBadRequest'; import CocktailStatisticItemCreateInput = Prisma.CocktailStatisticItemCreateInput; export default withHttpMethods({ [HTTPMethod.POST]: withWorkspacePermission([Role.USER], async (req: NextApiRequest, res: NextApiResponse, user, workspace) => { - const { cocktailId, cocktailCardId, actionSource, notes } = req.body; + const { cocktailId, cocktailCardId, actionSource, notes, ignoreQueue } = req.body; var cardId = cocktailCardId as string | undefined; if (cardId == 'search') { @@ -41,17 +42,74 @@ export default withHttpMethods({ actionSource: actionSource, }; - const queueItem = await prisma.cocktailQueue.findFirst({ - where: { - workspaceId: workspace.id, - cocktailId: cocktailId, - notes: notes, - }, - }); - if (queueItem) { - await prisma.cocktailQueue.delete({ where: { id: queueItem.id } }); + if (!ignoreQueue) { + const items = await prisma.cocktailQueue.findMany({ + where: { + workspaceId: workspace.id, + cocktailId: cocktailId, + }, + }); + // If there are items in the queue, then some could be removed + if (items.length != 0) { + /* + - If notes are provided, the cocktail is removed from the queue. If no cocktail was found - ignore the queue, + - If no note was provided: + - if only cocktails with no notes are in the queue, the cocktail gets removed from the queue, + - if there are cocktails with notes in the queue, return an error "ask user wich one to remove" (oldest + */ + if (notes) { + const queueItem = await prisma.cocktailQueue.findFirst({ + where: { + workspaceId: workspace.id, + cocktailId: cocktailId, + notes: notes, + }, + orderBy: { + createdAt: 'asc', + }, + }); + if (queueItem) { + await prisma.cocktailQueue.delete({ where: { id: queueItem.id } }); + } + } else { + const queueItems = await prisma.cocktailQueue.findMany({ + where: { + workspaceId: workspace.id, + cocktailId: cocktailId, + notes: null, + }, + orderBy: { + createdAt: 'asc', + }, + }); + if (queueItems.length > 0) { + await prisma.cocktailQueue.delete({ where: { id: queueItems[0].id } }); + } else { + // list with the oldest cocktail grouped by cocktailId and notes + const oldestEntries = await prisma.cocktailQueue.groupBy({ + by: ['cocktailId', 'notes'], + where: { + workspaceId: workspace.id, + cocktailId: cocktailId, + }, + _min: { + createdAt: true, // Wir wollen das älteste Datum + id: true, // Option, um den Datensatz selbst zu identifizieren + }, + orderBy: { + _min: { + createdAt: 'asc', + }, + }, + }); + + return res.status(400).json({ message: StatisticBadRequestMessage, data: oldestEntries }); + } + } + } } + // Add the statistic item const result = await prisma.cocktailStatisticItem.create({ data: input, }); diff --git a/pages/workspaces/[workspaceId]/index.tsx b/pages/workspaces/[workspaceId]/index.tsx index fa627ec9..e688922a 100644 --- a/pages/workspaces/[workspaceId]/index.tsx +++ b/pages/workspaces/[workspaceId]/index.tsx @@ -355,11 +355,14 @@ export default function OverviewPage() {
- {cocktailQueueItem.count}x {cocktailQueueItem.cocktailName} {cocktailQueueItem.notes && mit Notiz} +
+ {cocktailQueueItem.count}x {cocktailQueueItem.cocktailName} +
+ {cocktailQueueItem.notes && mit Notiz} (seit {new Date(cocktailQueueItem.oldestTimestamp).toFormatTimeString()} Uhr)
- {cocktailQueueItem.notes &&
{cocktailQueueItem.notes}
} + {cocktailQueueItem.notes && Notiz: {cocktailQueueItem.notes}}
-
+