Skip to content

Commit

Permalink
Merged branch feature/better-view-of-inbox-tasks-in-timeplans into de…
Browse files Browse the repository at this point in the history
…velop
  • Loading branch information
horia141 committed Jun 21, 2024
1 parent 4fc28e8 commit 346be85
Show file tree
Hide file tree
Showing 2 changed files with 203 additions and 68 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import type {
BigPlan,
Workspace} from "@jupiter/webapi-client";
import type { BigPlan, Workspace } from "@jupiter/webapi-client";
import {
ApiError,
TimePlanActivityTarget,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
);
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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(),
};
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -177,77 +210,129 @@ 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 (
<LeafPanel
key={`time-plan-${id}/add-from-current-inbox-tasks`}
returnLocation={`/workspace/time-plans/${id}`}
initialExpansionState={LeafPanelExpansionState.LARGE}
>
<GlobalError actionResult={actionData} />
<SectionCard
<SectionCardNew
title="Current Inbox Tasks"
actions={[
<Button
key="add"
variant="contained"
disabled={!inputsEnabled}
type="submit"
name="intent"
value="add"
>
Add
</Button>,
<Button
key="add-and-override"
variant="outlined"
disabled={!inputsEnabled}
type="submit"
name="intent"
value="add-and-override"
>
Add and Override Dates
</Button>,
]}
actions={
<SectionActions
id="add-from-current-big-plans"
topLevelInfo={topLevelInfo}
inputsEnabled={inputsEnabled}
actions={[
ActionMultipleSpread(
ActionSingle({
text: "Add",
value: "add",
highlight: true,
}),
ActionSingle({
text: "Add And Override Dates",
value: "add-and-override",
})
),
FilterFewOptions(
selectedView,
[
{
value: View.MERGED,
text: "Merged",
icon: <ViewListIcon />,
},
{
value: View.BY_PROJECT,
text: "By Project",
icon: <FlareIcon />,
gatedOn: WorkspaceFeature.PROJECTS,
},
],
(selected) => setSelectedView(selected)
),
]}
/>
}
>
<Stack spacing={2} useFlexGap>
{sortedInboxTasks.map((inboxTask) => (
<InboxTaskCard
key={`inbox-task-${inboxTask.ref_id}`}
topLevelInfo={topLevelInfo}
inboxTask={inboxTask}
compact
allowSelect
selected={
alreadyIncludedInboxTaskRefIds.has(inboxTask.ref_id) ||
targetInboxTaskRefIds.has(inboxTask.ref_id)
{selectedView === View.MERGED && (
<InboxTaskList
topLevelInfo={topLevelInfo}
inboxTasks={sortedInboxTasks}
alreadyIncludedInboxTaskRefIds={alreadyIncludedInboxTaskRefIds}
targetInboxTaskRefIds={targetInboxTaskRefIds}
inboxTasksByRefId={entriesByRefId}
onSelected={(it) =>
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)
);
}}
/>
))}
</Stack>

const fullProjectName = computeProjectHierarchicalNameFromRoot(
p,
allProjectsByRefId
);

return (
<Box key={`project-${p.ref_id}`}>
<Divider>
<Typography variant="h6">{fullProjectName}</Typography>
</Divider>

<InboxTaskList
topLevelInfo={topLevelInfo}
inboxTasks={theInboxTasks}
alreadyIncludedInboxTaskRefIds={
alreadyIncludedInboxTaskRefIds
}
targetInboxTaskRefIds={targetInboxTaskRefIds}
inboxTasksByRefId={entriesByRefId}
onSelected={(it) =>
setTargetInboxTaskRefIds((itri) =>
toggleInboxTaskRefIds(itri, it.ref_id)
)
}
/>
</Box>
);
})}
</>
)}

<input
name="targetInboxTaskRefIds"
type="hidden"
value={Array.from(targetInboxTaskRefIds).join(",")}
/>
</SectionCard>
</SectionCardNew>
</LeafPanel>
);
}
Expand All @@ -263,6 +348,50 @@ export const ErrorBoundary = makeErrorBoundary(
}. Please try again!`
);

interface InboxTaskListProps {
topLevelInfo: TopLevelInfo;
inboxTasks: Array<InboxTask>;
alreadyIncludedInboxTaskRefIds: Set<string>;
targetInboxTaskRefIds: Set<string>;
inboxTasksByRefId: { [key: string]: InboxTaskParent };
onSelected: (it: InboxTask) => void;
}

function InboxTaskList(props: InboxTaskListProps) {
return (
<Stack spacing={2} useFlexGap>
{props.inboxTasks.map((inboxTask) => (
<InboxTaskCard
key={`inbox-task-${inboxTask.ref_id}`}
topLevelInfo={props.topLevelInfo}
inboxTask={inboxTask}
compact
allowSelect
selected={
props.alreadyIncludedInboxTaskRefIds.has(inboxTask.ref_id) ||
props.targetInboxTaskRefIds.has(inboxTask.ref_id)
}
showOptions={{
showProject: true,
showEisen: true,
showDifficulty: true,
showDueDate: true,
showParent: true,
}}
parent={props.inboxTasksByRefId[inboxTask.ref_id]}
onClick={(it) => {
if (props.alreadyIncludedInboxTaskRefIds.has(inboxTask.ref_id)) {
return;
}

props.onSelected(it);
}}
/>
))}
</Stack>
);
}

function toggleInboxTaskRefIds(
inboxTaskRefIds: Set<string>,
newRefId: string
Expand All @@ -285,3 +414,11 @@ function toggleInboxTaskRefIds(
return newInboxTaskRefIds;
}
}

function inferDefaultSelectedView(workspace: Workspace) {
if (!isWorkspaceFeatureAvailable(workspace, WorkspaceFeature.PROJECTS)) {
return View.MERGED;
}

return View.BY_PROJECT;
}

0 comments on commit 346be85

Please sign in to comment.