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;
+}