diff --git a/src/webui/app/routes/workspace/time-plans/$id/add-from-current-big-plans.tsx b/src/webui/app/routes/workspace/time-plans/$id/add-from-current-big-plans.tsx index 3ac8d96c..e6a5c69e 100644 --- a/src/webui/app/routes/workspace/time-plans/$id/add-from-current-big-plans.tsx +++ b/src/webui/app/routes/workspace/time-plans/$id/add-from-current-big-plans.tsx @@ -1,6 +1,4 @@ -import type { - BigPlan, - Workspace} from "@jupiter/webapi-client"; +import type { BigPlan, Workspace } from "@jupiter/webapi-client"; import { ApiError, TimePlanActivityTarget, @@ -46,7 +44,7 @@ import { standardShouldRevalidate } from "~/rendering/standard-should-revalidate import { useLoaderDataSafeForAnimation } from "~/rendering/use-loader-data-for-animation"; import { DisplayType } from "~/rendering/use-nested-entities"; import { getSession } from "~/sessions"; -import type { TopLevelInfo} from "~/top-level-context"; +import type { TopLevelInfo } from "~/top-level-context"; import { TopLevelInfoContext } from "~/top-level-context"; enum View { @@ -191,16 +189,16 @@ export default function TimePlanAddFromCurrentBigPlans() { loaderData.bigPlans.map((e) => e.big_plan) ); - const sortedProjects = sortProjectsByTreeOrder(loaderData.allProjects || []); - const allProjectsByRefId = new Map( - loaderData.allProjects?.map((p) => [p.ref_id, p]) - ); - const entriesByRefId: { [key: string]: BigPlanParent } = {}; for (const entry of loaderData.bigPlans) { entriesByRefId[entry.big_plan.ref_id] = bigPlanFindEntryToParent(entry); } + const sortedProjects = sortProjectsByTreeOrder(loaderData.allProjects || []); + const allProjectsByRefId = new Map( + loaderData.allProjects?.map((p) => [p.ref_id, p]) + ); + const [selectedView, setSelectedView] = useState( inferDefaultSelectedView(topLevelInfo.workspace) ); diff --git a/src/webui/app/routes/workspace/time-plans/$id/add-from-current-inbox-tasks.tsx b/src/webui/app/routes/workspace/time-plans/$id/add-from-current-inbox-tasks.tsx index 219cdb4d..084d77ea 100644 --- a/src/webui/app/routes/workspace/time-plans/$id/add-from-current-inbox-tasks.tsx +++ b/src/webui/app/routes/workspace/time-plans/$id/add-from-current-inbox-tasks.tsx @@ -1,11 +1,20 @@ -import { ApiError, TimePlanActivityTarget } from "@jupiter/webapi-client"; -import { Button, Stack } from "@mui/material"; +import type { + InboxTask, + Workspace} from "@jupiter/webapi-client"; +import { + ApiError, + TimePlanActivityTarget, + WorkspaceFeature, +} from "@jupiter/webapi-client"; +import FlareIcon from "@mui/icons-material/Flare"; +import ViewListIcon from "@mui/icons-material/ViewList"; +import { Box, Divider, Stack, Typography } from "@mui/material"; import type { ActionArgs, LoaderArgs } from "@remix-run/node"; import { json, redirect } from "@remix-run/node"; import type { ShouldRevalidateFunction } from "@remix-run/react"; import { useActionData, useParams, useTransition } from "@remix-run/react"; import { ReasonPhrases, StatusCodes } from "http-status-codes"; -import { useContext, useState } from "react"; +import { useContext, useEffect, useState } from "react"; import { z } from "zod"; import { parseForm, parseParams, parseQuery } from "zodix"; import { getLoggedInApiClient } from "~/api-clients"; @@ -14,20 +23,37 @@ import { makeCatchBoundary } from "~/components/infra/catch-boundary"; import { makeErrorBoundary } from "~/components/infra/error-boundary"; import { GlobalError } from "~/components/infra/errors"; import { LeafPanel } from "~/components/infra/layout/leaf-panel"; -import { SectionCard } from "~/components/infra/section-card"; +import { + ActionMultipleSpread, + ActionSingle, + FilterFewOptions, + SectionActions, +} from "~/components/infra/section-actions"; +import { SectionCardNew } from "~/components/infra/section-card-new"; import { validationErrorToUIErrorInfo } from "~/logic/action-result"; import type { InboxTaskParent } from "~/logic/domain/inbox-task"; import { inboxTaskFindEntryToParent, sortInboxTasksByEisenAndDifficulty, } from "~/logic/domain/inbox-task"; +import { + computeProjectHierarchicalNameFromRoot, + sortProjectsByTreeOrder, +} from "~/logic/domain/project"; +import { isWorkspaceFeatureAvailable } from "~/logic/domain/workspace"; import { LeafPanelExpansionState } from "~/rendering/leaf-panel-expansion"; import { standardShouldRevalidate } from "~/rendering/standard-should-revalidate"; import { useLoaderDataSafeForAnimation } from "~/rendering/use-loader-data-for-animation"; import { DisplayType } from "~/rendering/use-nested-entities"; import { getSession } from "~/sessions"; +import type { TopLevelInfo} from "~/top-level-context"; import { TopLevelInfoContext } from "~/top-level-context"; +enum View { + MERGED = "merged", + BY_PROJECT = "by-project", +} + const ParamsSchema = { id: z.string(), }; @@ -61,6 +87,12 @@ export async function loader({ request, params }: LoaderArgs) { } } + const summaryResponse = await getLoggedInApiClient( + session + ).getSummaries.getSummaries({ + include_projects: true, + }); + try { const timePlanResult = await getLoggedInApiClient( session @@ -83,6 +115,7 @@ export async function loader({ request, params }: LoaderArgs) { }); return json({ + allProjects: summaryResponse.projects || undefined, timePlan: timePlanResult.time_plan, activities: timePlanResult.activities, inboxTasks: inboxTasksResult.entries, @@ -177,6 +210,19 @@ export default function TimePlanAddFromCurrentInboxTasks() { entriesByRefId[entry.inbox_task.ref_id] = inboxTaskFindEntryToParent(entry); } + const sortedProjects = sortProjectsByTreeOrder(loaderData.allProjects || []); + const allProjectsByRefId = new Map( + loaderData.allProjects?.map((p) => [p.ref_id, p]) + ); + + const [selectedView, setSelectedView] = useState( + inferDefaultSelectedView(topLevelInfo.workspace) + ); + + useEffect(() => { + setSelectedView(inferDefaultSelectedView(topLevelInfo.workspace)); + }, [topLevelInfo]); + return ( - - Add - , - , - ]} + actions={ + , + }, + { + value: View.BY_PROJECT, + text: "By Project", + icon: , + gatedOn: WorkspaceFeature.PROJECTS, + }, + ], + (selected) => setSelectedView(selected) + ), + ]} + /> + } > - - {sortedInboxTasks.map((inboxTask) => ( - + setTargetInboxTaskRefIds((itri) => + toggleInboxTaskRefIds(itri, it.ref_id) + ) + } + /> + )} + + {selectedView === View.BY_PROJECT && ( + <> + {sortedProjects.map((p) => { + const theInboxTasks = sortedInboxTasks.filter( + (se) => entriesByRefId[se.ref_id]?.project?.ref_id === p.ref_id + ); + + if (theInboxTasks.length === 0) { + return null; } - showOptions={{ - showProject: true, - showEisen: true, - showDifficulty: true, - showDueDate: true, - showParent: true, - }} - parent={entriesByRefId[inboxTask.ref_id]} - onClick={(it) => { - if (alreadyIncludedInboxTaskRefIds.has(inboxTask.ref_id)) { - return; - } - - setTargetInboxTaskRefIds((itri) => - toggleInboxTaskRefIds(itri, it.ref_id) - ); - }} - /> - ))} - + + const fullProjectName = computeProjectHierarchicalNameFromRoot( + p, + allProjectsByRefId + ); + + return ( + + + {fullProjectName} + + + + setTargetInboxTaskRefIds((itri) => + toggleInboxTaskRefIds(itri, it.ref_id) + ) + } + /> + + ); + })} + + )} - + ); } @@ -263,6 +348,50 @@ export const ErrorBoundary = makeErrorBoundary( }. Please try again!` ); +interface InboxTaskListProps { + topLevelInfo: TopLevelInfo; + inboxTasks: Array; + alreadyIncludedInboxTaskRefIds: Set; + targetInboxTaskRefIds: Set; + inboxTasksByRefId: { [key: string]: InboxTaskParent }; + onSelected: (it: InboxTask) => void; +} + +function InboxTaskList(props: InboxTaskListProps) { + return ( + + {props.inboxTasks.map((inboxTask) => ( + { + if (props.alreadyIncludedInboxTaskRefIds.has(inboxTask.ref_id)) { + return; + } + + props.onSelected(it); + }} + /> + ))} + + ); +} + function toggleInboxTaskRefIds( inboxTaskRefIds: Set, newRefId: string @@ -285,3 +414,11 @@ function toggleInboxTaskRefIds( return newInboxTaskRefIds; } } + +function inferDefaultSelectedView(workspace: Workspace) { + if (!isWorkspaceFeatureAvailable(workspace, WorkspaceFeature.PROJECTS)) { + return View.MERGED; + } + + return View.BY_PROJECT; +}