From fe86f1e334f08578fc9b30a4f21b892c8b52680a Mon Sep 17 00:00:00 2001 From: cade Date: Tue, 16 Jul 2024 19:53:17 -0700 Subject: [PATCH] add initial menus in modals --- .../event-types/[eventTypeId]/edit/page.tsx | 28 +- .../event-types/[eventTypeId]/page.tsx | 17 +- .../[subjectId]/event-types/create/page.tsx | 21 +- .../sessions/[sessionId]/edit/page.tsx | 34 +- .../[order]/from-session/[sessionId]/page.tsx | 28 +- .../sessions/create/[order]/page.tsx | 24 +- app/_components/event-type-form.tsx | 21 +- app/_components/event-type-menu.tsx | 242 ++++++++++++-- app/_components/module-form-section.tsx | 68 ++-- app/_components/page-modal-header.tsx | 11 +- app/_components/session-form.tsx | 307 ++++++++++-------- app/_components/session-layout.tsx | 36 +- app/_components/session-menu.tsx | 65 ++-- app/_components/session-page.tsx | 38 ++- app/_components/sessions-page.tsx | 31 +- app/_components/training-plan-menu.tsx | 31 +- app/_queries/get-session-with-details.ts | 1 + app/_utilities/parse-sessions.ts | 51 +++ bun.lockb | Bin 361072 -> 363275 bytes package.json | 42 +-- 20 files changed, 707 insertions(+), 389 deletions(-) create mode 100644 app/_utilities/parse-sessions.ts diff --git a/app/(pages)/@modal/(md)/subjects/[subjectId]/event-types/[eventTypeId]/edit/page.tsx b/app/(pages)/@modal/(md)/subjects/[subjectId]/event-types/[eventTypeId]/edit/page.tsx index fde53ac2..a3b79d73 100644 --- a/app/(pages)/@modal/(md)/subjects/[subjectId]/event-types/[eventTypeId]/edit/page.tsx +++ b/app/(pages)/@modal/(md)/subjects/[subjectId]/event-types/[eventTypeId]/edit/page.tsx @@ -1,9 +1,9 @@ import EventTypeForm from '@/_components/event-type-form'; -import PageModalHeader from '@/_components/page-modal-header'; import getEventTypeWithInputs from '@/_queries/get-event-type-with-inputs'; import getSubject from '@/_queries/get-subject'; import listInputsBySubjectId from '@/_queries/list-inputs-by-subject-id'; import listSubjectsByTeamId from '@/_queries/list-subjects-by-team-id'; +import listTemplatesWithData from '@/_queries/list-templates-with-data'; import formatTitle from '@/_utilities/format-title'; interface PageProps { @@ -23,27 +23,33 @@ const Page = async ({ params: { eventTypeId, subjectId } }: PageProps) => { { data: eventType }, { data: availableInputs }, { data: subjects }, + { data: availableTemplates }, ] = await Promise.all([ getSubject(subjectId), getEventTypeWithInputs(eventTypeId), listInputsBySubjectId(subjectId), listSubjectsByTeamId(), + listTemplatesWithData(), ]); - if (!subject || !eventType || !availableInputs || !subjects) { + if ( + !subject || + !eventType || + !availableInputs || + !subjects || + !availableTemplates + ) { return null; } return ( - <> - - - + ); }; diff --git a/app/(pages)/@modal/(md)/subjects/[subjectId]/event-types/[eventTypeId]/page.tsx b/app/(pages)/@modal/(md)/subjects/[subjectId]/event-types/[eventTypeId]/page.tsx index 98b396db..28ed3e23 100644 --- a/app/(pages)/@modal/(md)/subjects/[subjectId]/event-types/[eventTypeId]/page.tsx +++ b/app/(pages)/@modal/(md)/subjects/[subjectId]/event-types/[eventTypeId]/page.tsx @@ -1,4 +1,5 @@ import EventCard from '@/_components/event-card'; +import EventTypeMenu from '@/_components/event-type-menu'; import PageModalHeader from '@/_components/page-modal-header'; import getCurrentUser from '@/_queries/get-current-user'; import getEventTypeWithInputsAndOptions from '@/_queries/get-event-type-with-inputs-and-options'; @@ -22,14 +23,26 @@ const Page = async ({ params: { eventTypeId, subjectId } }: PageProps) => { ]); if (!subject || !eventType) return null; + const isTeamMember = !!user && subject.team_id === user.id; return ( <> - + + ) + } + title={eventType.name as string} + /> diff --git a/app/(pages)/@modal/(md)/subjects/[subjectId]/event-types/create/page.tsx b/app/(pages)/@modal/(md)/subjects/[subjectId]/event-types/create/page.tsx index b50bc238..2ee1bc8a 100644 --- a/app/(pages)/@modal/(md)/subjects/[subjectId]/event-types/create/page.tsx +++ b/app/(pages)/@modal/(md)/subjects/[subjectId]/event-types/create/page.tsx @@ -1,5 +1,4 @@ import EventTypeForm from '@/_components/event-type-form'; -import PageModalHeader from '@/_components/page-modal-header'; import getSubject from '@/_queries/get-subject'; import listInputsBySubjectId from '@/_queries/list-inputs-by-subject-id'; import listSubjectsByTeamId from '@/_queries/list-subjects-by-team-id'; @@ -20,28 +19,26 @@ const Page = async ({ params: { subjectId } }: PageProps) => { const [ { data: subject }, { data: availableInputs }, - { data: availableTemplates }, { data: subjects }, + { data: availableTemplates }, ] = await Promise.all([ getSubject(subjectId), listInputsBySubjectId(subjectId), - listTemplatesWithData(), listSubjectsByTeamId(), + listTemplatesWithData(), ]); - if (!subject || !availableInputs || !availableTemplates || !subjects) { + if (!subject || !availableInputs || !subjects || !availableTemplates) { return null; } return ( - <> - - - + ); }; diff --git a/app/(pages)/@modal/(md)/subjects/[subjectId]/training-plans/[missionId]/sessions/[sessionId]/edit/page.tsx b/app/(pages)/@modal/(md)/subjects/[subjectId]/training-plans/[missionId]/sessions/[sessionId]/edit/page.tsx index 85d20a20..9f0504dd 100644 --- a/app/(pages)/@modal/(md)/subjects/[subjectId]/training-plans/[missionId]/sessions/[sessionId]/edit/page.tsx +++ b/app/(pages)/@modal/(md)/subjects/[subjectId]/training-plans/[missionId]/sessions/[sessionId]/edit/page.tsx @@ -1,7 +1,4 @@ -import BackButton from '@/_components/back-button'; -import PageModalHeader from '@/_components/page-modal-header'; import SessionForm from '@/_components/session-form'; -import SessionLayout from '@/_components/session-layout'; import getCurrentUser from '@/_queries/get-current-user'; import getSession from '@/_queries/get-session'; import getSubject from '@/_queries/get-subject'; @@ -59,29 +56,14 @@ const Page = async ({ } return ( - <> - - - - - - Close - - + ); }; diff --git a/app/(pages)/@modal/(md)/subjects/[subjectId]/training-plans/[missionId]/sessions/create/[order]/from-session/[sessionId]/page.tsx b/app/(pages)/@modal/(md)/subjects/[subjectId]/training-plans/[missionId]/sessions/create/[order]/from-session/[sessionId]/page.tsx index e4d9da98..4dc527dc 100644 --- a/app/(pages)/@modal/(md)/subjects/[subjectId]/training-plans/[missionId]/sessions/create/[order]/from-session/[sessionId]/page.tsx +++ b/app/(pages)/@modal/(md)/subjects/[subjectId]/training-plans/[missionId]/sessions/create/[order]/from-session/[sessionId]/page.tsx @@ -1,7 +1,5 @@ import BackButton from '@/_components/back-button'; -import PageModalHeader from '@/_components/page-modal-header'; import SessionForm from '@/_components/session-form'; -import SessionLayout from '@/_components/session-layout'; import getCurrentUser from '@/_queries/get-current-user'; import getSession from '@/_queries/get-session'; import getSubject from '@/_queries/get-subject'; @@ -60,26 +58,16 @@ const Page = async ({ return ( <> - - - - + /> Close diff --git a/app/(pages)/@modal/(md)/subjects/[subjectId]/training-plans/[missionId]/sessions/create/[order]/page.tsx b/app/(pages)/@modal/(md)/subjects/[subjectId]/training-plans/[missionId]/sessions/create/[order]/page.tsx index ea2954db..a7451cba 100644 --- a/app/(pages)/@modal/(md)/subjects/[subjectId]/training-plans/[missionId]/sessions/create/[order]/page.tsx +++ b/app/(pages)/@modal/(md)/subjects/[subjectId]/training-plans/[missionId]/sessions/create/[order]/page.tsx @@ -1,7 +1,5 @@ import BackButton from '@/_components/back-button'; -import PageModalHeader from '@/_components/page-modal-header'; import SessionForm from '@/_components/session-form'; -import SessionLayout from '@/_components/session-layout'; import getCurrentUser from '@/_queries/get-current-user'; import getSubject from '@/_queries/get-subject'; import getTrainingPlanWithSessions from '@/_queries/get-training-plan-with-sessions'; @@ -53,24 +51,14 @@ const Page = async ({ params: { missionId, order, subjectId } }: PageProps) => { return ( <> - - - - + /> Close diff --git a/app/_components/event-type-form.tsx b/app/_components/event-type-form.tsx index 707ad464..2aa3a4f4 100644 --- a/app/_components/event-type-form.tsx +++ b/app/_components/event-type-form.tsx @@ -2,6 +2,7 @@ import BackButton from '@/_components/back-button'; import Button from '@/_components/button'; +import EventTypeMenu from '@/_components/event-type-menu'; import Input from '@/_components/input'; import InputForm from '@/_components/input-form'; import PageModalHeader from '@/_components/page-modal-header'; @@ -14,6 +15,7 @@ import { GetEventTypeWithInputsData } from '@/_queries/get-event-type-with-input import { GetInputData } from '@/_queries/get-input'; import { ListInputsBySubjectIdData } from '@/_queries/list-inputs-by-subject-id'; import { ListSubjectsByTeamIdData } from '@/_queries/list-subjects-by-team-id'; +import { ListTemplatesWithDataData } from '@/_queries/list-templates-with-data'; import getFormCacheKey from '@/_utilities/get-form-cache-key'; import { Dialog, DialogPanel } from '@headlessui/react'; import { useRouter } from 'next/navigation'; @@ -22,9 +24,10 @@ import { Controller, useFieldArray } from 'react-hook-form'; interface EventTypeFormProps { availableInputs: NonNullable; + availableTemplates?: NonNullable; eventType?: NonNullable; - subjects: NonNullable; subjectId: string; + subjects: NonNullable; } type EventTypeFormValues = { @@ -35,9 +38,10 @@ type EventTypeFormValues = { const EventTypeForm = ({ availableInputs, + availableTemplates, eventType, - subjects, subjectId, + subjects, }: EventTypeFormProps) => { const [createInputModal, setCreateInputModal] = useState>(null); @@ -60,6 +64,19 @@ const EventTypeForm = ({ return ( <> + + availableInputs={availableInputs} + availableTemplates={availableTemplates} + eventTypeId={eventType?.id} + form={form} + subjectId={subjectId} + subjects={subjects} + /> + } + title={(eventType?.name as string) ?? 'New event type'} + />
diff --git a/app/_components/event-type-menu.tsx b/app/_components/event-type-menu.tsx index a043b3e7..14ff580e 100644 --- a/app/_components/event-type-menu.tsx +++ b/app/_components/event-type-menu.tsx @@ -1,61 +1,237 @@ 'use client'; import Alert from '@/_components/alert'; +import Button from '@/_components/button'; import DropdownMenu from '@/_components/dropdown-menu'; +import IconButton from '@/_components/icon-button'; +import PageModalHeader from '@/_components/page-modal-header'; +import Select from '@/_components/select'; +import TemplateForm from '@/_components/template-form'; import deleteEventType from '@/_mutations/delete-event-type'; +import { GetTemplateData } from '@/_queries/get-template'; +import { ListInputsBySubjectIdData } from '@/_queries/list-inputs-by-subject-id'; +import { ListSubjectsByTeamIdData } from '@/_queries/list-subjects-by-team-id'; +import { ListTemplatesWithDataData } from '@/_queries/list-templates-with-data'; +import { TemplateDataJson } from '@/_types/template-data-json'; +import forceArray from '@/_utilities/force-array'; import DocumentDuplicateIcon from '@heroicons/react/24/outline/DocumentDuplicateIcon'; +import DocumentTextIcon from '@heroicons/react/24/outline/DocumentTextIcon'; import EllipsisVerticalIcon from '@heroicons/react/24/outline/EllipsisVerticalIcon'; import PencilIcon from '@heroicons/react/24/outline/PencilIcon'; import TrashIcon from '@heroicons/react/24/outline/TrashIcon'; import { useToggle } from '@uidotdev/usehooks'; +import { useRouter } from 'next/navigation'; +import { useState } from 'react'; +import { FieldValues, Path, PathValue, UseFormReturn } from 'react-hook-form'; -interface EventTypeMenuProps { - eventTypeId: string; +import { + Description, + Dialog, + DialogPanel, + DialogTitle, +} from '@headlessui/react'; + +interface EventTypeMenuProps { + availableInputs?: NonNullable; + availableTemplates?: NonNullable; + eventTypeId?: string; + form?: UseFormReturn; + isView?: boolean; subjectId: string; + subjects?: NonNullable; } -const EventTypeMenu = ({ eventTypeId, subjectId }: EventTypeMenuProps) => { +const EventTypeMenu = ({ + availableInputs, + availableTemplates, + eventTypeId, + form, + isView, + subjectId, + subjects, +}: EventTypeMenuProps) => { const [deleteAlert, toggleDeleteAlert] = useToggle(false); + const [createTemplateModal, setCreateTemplateModal] = + useState>(null); + + const [useTemplateModal, toggleUseTemplateModal] = useToggle(false); + + const router = useRouter(); + return ( <> - deleteEventType(eventTypeId)} - /> -
- + form || isView ? ( + } /> + ) : ( +
+
+ +
-
+ ) } > - - - - Edit - - - - New template - - toggleDeleteAlert(true)}> - - Delete - + + {form && ( + <> + toggleUseTemplateModal()}> + + Use template + + { + const { content, inputs, name } = form.getValues(); + + setCreateTemplateModal({ + data: { + content, + inputIds: inputs.map(({ id }: { id: string }) => id), + }, + name, + }); + }} + > + + New template + + + )} + {eventTypeId && ( + <> + {!form && ( + <> + + + Edit + + + + New template + + + )} + toggleDeleteAlert(true)}> + + Delete + + + )}
+ {form && availableInputs && availableTemplates && subjects && ( + <> + +
+
+
+ + Use template + + Selecting a template will overwrite any existing event type + values. + +
+ 'No templates.'} - onChange={(t) => { - const template = - t as NonNullable[0]; +
+ - -
-
    - { - const { active, over } = event; - - if (over && active.id !== over.id) { - modulesArray.move( - modulesArray.fields.findIndex((f) => f.key === active.id), - modulesArray.fields.findIndex((f) => f.key === over.id), - ); + if (res?.error) { + form.setError('root', { message: res.error, type: 'custom' }); + return; } - }} - sensors={sensors} - > - eventType.key)} - strategy={verticalListSortingStrategy} + + router.back(); + }), + )} + > +
    + +
-
- - Modules break up your session and allow you to capture inputs at - different points. You can add as many modules as you need. - - -
- {form.formState.errors.root && ( -
- {form.formState.errors.root.message} + + {scheduledFor ? ( + + ) : ( + 'Schedule' + )} +
- )} -
- {draft && ( +
    + { + const { active, over } = event; + + if (over && active.id !== over.id) { + modulesArray.move( + modulesArray.fields.findIndex((f) => f.key === active.id), + modulesArray.fields.findIndex((f) => f.key === over.id), + ); + } + }} + sensors={sensors} + > + eventType.key)} + strategy={verticalListSortingStrategy} + > + {modulesArray.fields.map((module, eventTypeIndex) => ( + + availableInputs={availableInputs} + availableTemplates={availableTemplates} + eventTypeArray={modulesArray} + eventTypeIndex={eventTypeIndex} + eventTypeKey={module.key} + form={form} + hasOnlyOne={modulesArray.fields.length === 1} + key={module.key} + subjectId={subjectId} + subjects={subjects} + /> + ))} + + +
+
+ + Modules break up your session and allow you to capture inputs at + different points. You can add as many modules as you need. + +
+ {form.formState.errors.root && ( +
+ {form.formState.errors.root.message} +
+ )} +
+ {draft && ( + + )} + - )} - -
- form={form} /> - +
+ form={form} /> + +
diff --git a/app/_components/session-layout.tsx b/app/_components/session-layout.tsx index 5dcdec03..1a5b5a0c 100644 --- a/app/_components/session-layout.tsx +++ b/app/_components/session-layout.tsx @@ -10,27 +10,33 @@ import { ReactNode } from 'react'; interface SessionLayoutProps { children: ReactNode; + highestOrder: number; isArchived?: boolean; isCreate?: boolean; isEdit?: boolean; isPublic?: boolean; isTeamMember: boolean; missionId: string; + nextSessionId: string | null; order?: string; + previousSessionId: string | null; sessionId?: string; sessions: NonNullable['sessions']; subjectId: string; } -const SessionLayout = async ({ +const SessionLayout = ({ children, + highestOrder, isArchived, isCreate, isEdit, isPublic, isTeamMember, missionId, + nextSessionId, order, + previousSessionId, sessionId, sessions, subjectId, @@ -41,32 +47,6 @@ const SessionLayout = async ({ if (typeof sessionOrder === 'undefined') return null; const shareOrSubjects = isPublic ? 'share' : 'subjects'; - // eslint-disable-next-line prefer-const - let { highestOrder, nextSessionId, previousSessionId } = sessions.reduce( - (acc, session, i) => { - acc.highestOrder = Math.max(acc.highestOrder, session.order); - - if (currentSession) { - if (currentSession.id === session.id) { - acc.nextSessionId = sessions[i + 1]?.id; - acc.previousSessionId = sessions[i - 1]?.id; - } - } else { - if (session.order === sessionOrder) { - acc.nextSessionId = sessions[i + 1]?.id; - acc.previousSessionId = sessions[i]?.id; - } - } - - return acc; - }, - { highestOrder: -1, nextSessionId: null, previousSessionId: null } as { - highestOrder: number; - nextSessionId: string | null; - previousSessionId: string | null; - }, - ); - if ( isEditOrCreate && sessions.length > 0 && @@ -112,7 +92,6 @@ const SessionLayout = async ({ @@ -123,7 +102,6 @@ const SessionLayout = async ({ diff --git a/app/_components/session-menu.tsx b/app/_components/session-menu.tsx index 5906d458..8acb12c1 100644 --- a/app/_components/session-menu.tsx +++ b/app/_components/session-menu.tsx @@ -2,8 +2,10 @@ import Alert from '@/_components/alert'; import DropdownMenu from '@/_components/dropdown-menu'; +import IconButton from '@/_components/icon-button'; import deleteSession from '@/_mutations/delete-session'; import moveSession from '@/_mutations/move-session'; +import { GetSessionWithDetailsData } from '@/_queries/get-session-with-details'; import { GetTrainingPlanWithSessionsData } from '@/_queries/get-training-plan-with-sessions'; import ArrowDownIcon from '@heroicons/react/24/outline/ArrowDownIcon'; import ArrowUpIcon from '@heroicons/react/24/outline/ArrowUpIcon'; @@ -12,46 +14,63 @@ import EllipsisVerticalIcon from '@heroicons/react/24/outline/EllipsisVerticalIc import PencilIcon from '@heroicons/react/24/outline/PencilIcon'; import TrashIcon from '@heroicons/react/24/outline/TrashIcon'; import { useToggle } from '@uidotdev/usehooks'; +import { useRouter } from 'next/navigation'; import { useTransition } from 'react'; +import { FieldValues, UseFormReturn } from 'react-hook-form'; -interface SessionMenuProps { +interface SessionMenuProps { + form?: UseFormReturn; highestPublishedOrder: number; + isView?: boolean; missionId: string; nextSessionOrder: number; - session: NonNullable['sessions'][0]; + session: + | NonNullable['sessions'][0] + | NonNullable; subjectId: string; } -const SessionMenu = ({ +const SessionMenu = ({ + form, highestPublishedOrder, + isView, missionId, nextSessionOrder, session, subjectId, -}: SessionMenuProps) => { +}: SessionMenuProps) => { const [deleteAlert, toggleDeleteAlert] = useToggle(false); const [isMoveLeftTransitioning, startMoveLeftTransition] = useTransition(); const [isMoveRightTransitioning, startMoveRightTransition] = useTransition(); + const router = useRouter(); return ( <> -
- + form || isView ? ( + } /> + ) : ( +
+
+ +
-
+ ) } > - - - - Edit - + + {!form && ( + + + Edit + + )} - deleteSession({ + onConfirm={async () => { + await deleteSession({ currentOrder: session.order, missionId: missionId, sessionId: session.id, - }) - } + }); + + if (form || isView) { + router.replace( + `/subjects/${subjectId}/training-plans/${missionId}`, + ); + } + }} isOpen={deleteAlert} onClose={toggleDeleteAlert} /> diff --git a/app/_components/session-page.tsx b/app/_components/session-page.tsx index dbbda011..c64fb6dc 100644 --- a/app/_components/session-page.tsx +++ b/app/_components/session-page.tsx @@ -4,6 +4,7 @@ import Empty from '@/_components/empty'; import ModuleCard from '@/_components/module-card'; import PageModalHeader from '@/_components/page-modal-header'; import SessionLayout from '@/_components/session-layout'; +import SessionMenu from '@/_components/session-menu'; import ViewAllSessionsButton from '@/_components/view-all-sessions-button'; import getCurrentUser from '@/_queries/get-current-user'; import getPublicSessionWithDetails from '@/_queries/get-public-session-with-details'; @@ -13,6 +14,7 @@ import getSessionWithDetails from '@/_queries/get-session-with-details'; import getSubject from '@/_queries/get-subject'; import getTrainingPlanWithSessions from '@/_queries/get-training-plan-with-sessions'; import firstIfArray from '@/_utilities/first-if-array'; +import parseSessions from '@/_utilities/parse-sessions'; import CalendarDaysIcon from '@heroicons/react/24/outline/CalendarDaysIcon'; interface SessionPageProps { @@ -28,7 +30,7 @@ const SessionPage = async ({ sessionId, subjectId, }: SessionPageProps) => { - const [{ data: subject }, { data: mission }, { data: session }, user] = + const [{ data: subject }, { data: trainingPlan }, { data: session }, user] = await Promise.all([ isPublic ? getPublicSubject(subjectId) : getSubject(subjectId), isPublic @@ -40,12 +42,35 @@ const SessionPage = async ({ getCurrentUser(), ]); - if (!subject || !mission || !session) return null; + if (!subject || !trainingPlan || !session) return null; const isTeamMember = !!user && subject.team_id === user.id; + const { + highestOrder, + highestPublishedOrder, + nextSessionId, + previousSessionId, + } = parseSessions({ + currentSession: session, + sessionOrder: session.order, + sessions: trainingPlan.sessions, + }); + return ( <> + ) + } subtitle={ } - title={mission.name} + title={trainingPlan.name} /> {session.scheduled_for && @@ -103,7 +131,7 @@ const SessionPage = async ({ isArchived={subject.archived} isPublic={isPublic} isTeamMember={isTeamMember} - mission={mission} + mission={trainingPlan} subjectId={subjectId} user={user} /> diff --git a/app/_components/sessions-page.tsx b/app/_components/sessions-page.tsx index 0c795ab8..2726250f 100644 --- a/app/_components/sessions-page.tsx +++ b/app/_components/sessions-page.tsx @@ -4,12 +4,13 @@ import DateTime from '@/_components/date-time'; import Empty from '@/_components/empty'; import PageModalHeader from '@/_components/page-modal-header'; import SessionMenu from '@/_components/session-menu'; +import TrainingPlanMenu from '@/_components/training-plan-menu'; import getCurrentUser from '@/_queries/get-current-user'; import getPublicSubject from '@/_queries/get-public-subject'; import getPublicTrainingPlanWithSessionsAndEvents from '@/_queries/get-public-training-plan-with-sessions-and-events'; import getSubject from '@/_queries/get-subject'; import getTrainingPlanWithSessionsAndEvents from '@/_queries/get-training-plan-with-sessions-and-events'; -import getHighestPublishedOrder from '@/_utilities/get-highest-published-order'; +import parseSessions from '@/_utilities/parse-sessions'; import ArrowUpRightIcon from '@heroicons/react/24/outline/ArrowUpRightIcon'; import InformationCircleIcon from '@heroicons/react/24/outline/InformationCircleIcon'; import PlusIcon from '@heroicons/react/24/outline/PlusIcon'; @@ -41,26 +42,26 @@ const SessionsPage = async ({ if (!mission) return null; - const { highestOrder, sessionsReversed } = mission.sessions.reduce( - (acc, session, i) => { - acc.highestOrder = Math.max(acc.highestOrder, session.order); + const { highestOrder, highestPublishedOrder, sessionsReversed } = + parseSessions({ sessions: mission.sessions }); - acc.sessionsReversed.push( - mission.sessions[mission.sessions.length - i - 1], - ); - - return acc; - }, - { highestOrder: -1, sessionsReversed: [] as typeof mission.sessions }, - ); - - const highestPublishedOrder = getHighestPublishedOrder(mission.sessions); const nextSessionOrder = highestOrder + 1; const shareOrSubjects = isPublic ? 'share' : 'subjects'; return ( <> - + + ) + } + title={mission.name} + /> {!isPublic && !subject.archived && isTeamMember && (