diff --git a/core/app/c/[communitySlug]/stages/components/StageList.tsx b/core/app/c/[communitySlug]/stages/components/StageList.tsx index 169dc788ce..9381e86b44 100644 --- a/core/app/c/[communitySlug]/stages/components/StageList.tsx +++ b/core/app/c/[communitySlug]/stages/components/StageList.tsx @@ -5,86 +5,109 @@ import { Fragment } from "react"; import { Button } from "ui"; import PubRow from "~/app/components/PubRow"; import { getPubUsers } from "~/lib/permissions"; -import { StagePayload, UserLoginData } from "~/lib/types"; +import { StageIndex, StagePayload, UserLoginData } from "~/lib/types"; import { StagePubActions } from "./StagePubActions"; +import { stageSources } from "~/lib/pubStages"; -type Props = { stages: StagePayload[]; token: string; loginData: UserLoginData }; +type Props = { + stageWorkflows: StagePayload[][]; + stageIndex: StageIndex; + token: string; + loginData: UserLoginData; +}; type IntegrationAction = { text: string; href: string; kind?: "stage" }; -const StageList: React.FC = function ({ stages, token, loginData }) { +function StageList(props: Props) { return (
- {stages.map((stage) => { - const users = getPubUsers(stage.permissions); - const sources = stage.moveConstraintSources.map( - (stage) => stages.find((s) => s.id === stage.stageId)! - ); - const destinations = stage.moveConstraints.map((stage) => stage.destination); - return ( -
-
-

{stage.name}

- {stage.integrationInstances.map((instance) => { - if (!Array.isArray(instance.integration.actions)) { - return null; - } + { + props.stageWorkflows.map((stages) => { + return ( +
+ {stages.map((stage) => { + const users = getPubUsers(stage.permissions); + const sources = stageSources(stage, props.stageIndex); + const destinations = stage.moveConstraints.map( + (stage) => stage.destination + ); return ( - - {instance.integration.actions?.map( - (action: IntegrationAction) => { - if (action.kind === "stage") { - const href = new URL(action.href); - href.searchParams.set( - "instanceId", - instance.id - ); - href.searchParams.set("token", token); - return ( - - ); +
+
+

+ {stage.name} +

+ {stage.integrationInstances.map((instance) => { + if (!Array.isArray(instance.integration.actions)) { + return null; } - } - )} - + return ( + + {instance.integration.actions?.map( + (action: IntegrationAction) => { + if (action.kind === "stage") { + const href = new URL( + action.href + ); + href.searchParams.set( + "instanceId", + instance.id + ); + href.searchParams.set( + "token", + props.token + ); + return ( + + ); + } + } + )} + + ); + })} +
+ {stage.pubs.map((pub, index, list) => { + return ( + + + } + /> + {index < list.length - 1 &&
} +
+ ); + })} +
); })}
- {stage.pubs.map((pub, index, list) => { - return ( - - - } - /> - {index < list.length - 1 &&
} -
- ); - })} -
- ); - })} + ); + })[0] + }
); -}; +} export default StageList; diff --git a/core/app/c/[communitySlug]/stages/dashboard/StageManagement.tsx b/core/app/c/[communitySlug]/stages/dashboard/StageManagement.tsx index 6d33480315..fc35e51061 100644 --- a/core/app/c/[communitySlug]/stages/dashboard/StageManagement.tsx +++ b/core/app/c/[communitySlug]/stages/dashboard/StageManagement.tsx @@ -3,10 +3,12 @@ import { useState } from "react"; import { Button, Input, Tabs, TabsContent, TabsList, TabsTrigger } from "ui"; import StagesEditor from "./StagesEditor"; +import { StagePayload, StageIndex } from "~/lib/types"; type Props = { community: any; - stages: any; + stageWorkflows: StagePayload[][]; + stageIndex: StageIndex; }; export default function StageManagement(props: Props) { @@ -42,7 +44,10 @@ export default function StageManagement(props: Props) {
- +
diff --git a/core/app/c/[communitySlug]/stages/dashboard/StagesEditor.tsx b/core/app/c/[communitySlug]/stages/dashboard/StagesEditor.tsx index 3bd1882d4c..47b9319b25 100644 --- a/core/app/c/[communitySlug]/stages/dashboard/StagesEditor.tsx +++ b/core/app/c/[communitySlug]/stages/dashboard/StagesEditor.tsx @@ -18,7 +18,8 @@ import { TabsTrigger, } from "ui"; import * as z from "zod"; -import { StagePayload } from "~/lib/types"; +import { stageSources } from "~/lib/pubStages"; +import { StageIndex, StagePayload } from "~/lib/types"; const schema = z.object({ stageName: z.string(), @@ -43,14 +44,13 @@ const schema = z.object({ }); type Props = { - stages: StagePayload[]; + stageWorkflows: StagePayload[][]; + stageIndex: StageIndex; }; const StagesEditor = (props: Props) => { - const [selectedStage, setSelectedStage] = useState(props.stages[0]); // Set the initial selected stage. - const sources = selectedStage.moveConstraintSources.map( - (stage) => props.stages.find((s) => s.id === stage.stageId)! - ); + const [selectedStage, setSelectedStage] = useState(props.stageWorkflows[0][0]); // Set the initial selected stage. + const sources = stageSources(selectedStage, props.stageIndex); const handleStageChange = (newStage: StagePayload) => { setSelectedStage(newStage); }; @@ -77,179 +77,217 @@ const StagesEditor = (props: Props) => { return (
- - {props.stages.map((stage) => { - return ( - - handleStageChange(stage)}> - {stage.name} - - - ); - })} - {props.stages.map((stage) => { + { + props.stageWorkflows.map((stages) => { return ( - -
- -
-
-

- {selectedStage.name} -

-

- Stage Settings This form contains fields used to - edit your surrent stages. -

-
- ( - -
- Stage Name - - - - - Name of the stage - - -
-
- )} - /> - ( - -
- Stage Order - - - - - Stage order - - -
-
- )} - /> - ( - -
- - Moves to - - - These are stages {selectedStage.name}{" "} - can move to - +
+ + {stages.map((stage) => { + return ( + + handleStageChange(stage)} + > + {stage.name} + + + ); + })} + {stages.map((stage) => { + return ( + + + +
+
+

+ {selectedStage.name} +

+

+ Stage Settings This form contains + fields used to edit your surrent + stages. +

+
+ ( + +
+ + Stage Name + + + + + + Name of the stage + + +
+
+ )} + /> + ( + +
+ + Stage Order + + + + + + Stage order + + +
+
+ )} + /> + ( + +
+ + Moves to + + + These are stages{" "} + {selectedStage.name} can + move to + - {selectedStage.moveConstraints.map( - (stage) => { - return selectedStage.id === - stage.id ? null : ( - { - console.log( - selectedStage.moveConstraints.some( - (constraint) => - field.value.includes( - constraint - ) - ) - ); - console.log( - field.value?.some( - (constarint) => - selectedStage.moveConstraints.includes( - constarint - ) - ) - ); - return ( - - - - - - { - stage - .destination - .name + {selectedStage.moveConstraints.map( + (stage) => { + return selectedStage.id === + stage.id ? null : ( + - - ); - }} - /> + name="stageMoveConstraints" + render={({ + field, + }) => { + console.log( + selectedStage.moveConstraints.some( + ( + constraint + ) => + field.value.includes( + constraint + ) + ) + ); + console.log( + field.value?.some( + ( + constarint + ) => + selectedStage.moveConstraints.includes( + constarint + ) + ) + ); + return ( + + + + + + { + stage + .destination + .name + } + + + ); + }} + /> + ); + } + )} + +
+
+ )} + /> +
+

+ Moves from +

+

+ These are the stages{" "} + {selectedStage.name} can move back + from +

+ {sources.map((stage) => { + return ( +
+

+ handleStageChange( + stage + ) + } + className="text-blue-500" + > + {stage.name} +

+
); - } - )} - -
- - )} - /> -
-

Moves from

-

- These are the stages {selectedStage.name} can move - back from -

- {sources.map((stage) => { - return ( -
-

handleStageChange(stage)} - className="text-blue-500" - > - {stage.name} -

+ })} +
+
+ + +
- ); - })} -
-
- - -
-
- - - + + + + ); + })} + +
); - })} - + })[0] + }
); }; diff --git a/core/app/c/[communitySlug]/stages/dashboard/page.tsx b/core/app/c/[communitySlug]/stages/dashboard/page.tsx index ff58ac8dc2..4ac3ac83c7 100644 --- a/core/app/c/[communitySlug]/stages/dashboard/page.tsx +++ b/core/app/c/[communitySlug]/stages/dashboard/page.tsx @@ -1,6 +1,7 @@ import prisma from "~/prisma/db"; import StageManagement from "./StageManagement"; import { stageInclude } from "~/lib/types"; +import { stageList } from "~/lib/pubStages"; export default async function Page({ params }: { params: { communitySlug: string } }) { const community = await prisma.community.findUnique({ @@ -9,15 +10,22 @@ export default async function Page({ params }: { params: { communitySlug: string if (!community) { return null; } + const stages = await prisma.stage.findMany({ where: { communityId: community.id }, include: stageInclude, }); - + const { stageWorkflows, stageIndex } = stageList(stages); return ( <> -

Workflow: {params.communitySlug}

- +

+ Stages in {params.communitySlug} +

+ ); } diff --git a/core/app/c/[communitySlug]/stages/page.tsx b/core/app/c/[communitySlug]/stages/page.tsx index f69ccd075b..ab250bb2a9 100644 --- a/core/app/c/[communitySlug]/stages/page.tsx +++ b/core/app/c/[communitySlug]/stages/page.tsx @@ -3,6 +3,7 @@ import { createToken } from "~/lib/server/token"; import { stageInclude } from "~/lib/types"; import prisma from "~/prisma/db"; import StageList from "./components/StageList"; +import { stageList } from "~/lib/pubStages"; const getCommunityStages = async (communitySlug: string) => { const community = await prisma.community.findUnique({ @@ -31,13 +32,20 @@ export default async function Page({ params }: Props) { if (!stages) { return null; } + const { stageWorkflows, stageIndex } = stageList(stages); + return ( <>

Stages

-{" "} {/* Manage Stages */}
- + ); } diff --git a/core/lib/pubStages.ts b/core/lib/pubStages.ts new file mode 100644 index 0000000000..a0fe529355 --- /dev/null +++ b/core/lib/pubStages.ts @@ -0,0 +1,31 @@ +import { StagePayload, StageIndex } from "./types"; + +function createStageList(stage: StagePayload, stages: StageIndex, visited: Array) { + if (visited.includes(stage)) { + return; + } + visited.push(stage); + for (const constraint of stage.moveConstraints) { + const nextStage = stages[constraint.destinationId]; + createStageList(nextStage, stages, visited); + } +} + +export function stageList(stages: StagePayload[]) { + // create look up table for stages so you dont iterate in O(n) during recursion + const stageIndex: StageIndex = {}; + for (const stage of stages) { + stageIndex[stage.id] = stage; + } + const stageRoots = stages.filter((stage) => stage.moveConstraintSources.length === 0); + const stageWorkflows = stageRoots.map((stage) => { + const visited: Array = []; + createStageList(stage, stageIndex, visited); + return visited; + }); + return { stageIndex, stageWorkflows }; +} + +export function stageSources(stage: StagePayload, stageIndex: StageIndex) { + return stage.moveConstraintSources.map((stage) => stageIndex[stage.stageId]); +} diff --git a/core/lib/types.ts b/core/lib/types.ts index 1b9394e522..edd38f10c3 100644 --- a/core/lib/types.ts +++ b/core/lib/types.ts @@ -115,8 +115,10 @@ export const stageInclude = { } satisfies Prisma.StageInclude; export type StagePayload = Prisma.StageGetPayload<{ include: typeof stageInclude }>; +export type StageIndex = { [key: string]: StagePayload }; export type StagePayloadMoveConstraint = NonNullable; export type StagePayloadMoveConstraintDestination = StagePayloadMoveConstraint[number]["destination"]; export type IntegrationAction = { name: string; url: string; href: string }; + diff --git a/core/prisma/exampleCommunitySeeds/unjournal.ts b/core/prisma/exampleCommunitySeeds/unjournal.ts index 54ea2520d3..43d856205d 100644 --- a/core/prisma/exampleCommunitySeeds/unjournal.ts +++ b/core/prisma/exampleCommunitySeeds/unjournal.ts @@ -488,7 +488,7 @@ export default async function main(prisma: PrismaClient, communityUUID: string) }, }); - const stageIds = [...Array(7)].map((x) => uuidv4()); + const stageIds = [...Array(8)].map((x) => uuidv4()); await prisma.stage.createMany({ data: [ { @@ -497,6 +497,12 @@ export default async function main(prisma: PrismaClient, communityUUID: string) name: "Submitted", order: "aa", }, + { + id: stageIds[8], + communityId: communityUUID, + name: "Submitted to this lil bih", + order: "aa", + }, { id: stageIds[1], communityId: communityUUID,