Skip to content

Commit

Permalink
Merge pull request #281 from cabcookie:improve-task-icons
Browse files Browse the repository at this point in the history
Symbole für Aufgaben optimieren
  • Loading branch information
cabcookie authored Dec 20, 2024
2 parents bce5354 + c5c24ca commit e781556
Show file tree
Hide file tree
Showing 13 changed files with 193 additions and 66 deletions.
41 changes: 22 additions & 19 deletions api/useMeetingTodos.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { type Schema } from "@/amplify/data/resource";
import { isNotNil } from "@/helpers/functional";
import {
getTodoDoneOn,
getTodoId,
getTodoJson,
getTodoStatus,
notAnOrphan,
} from "@/helpers/todos";
import { generateClient, SelectionSet } from "aws-amplify/data";
import { filter, flatMap, flow, get, identity, map, sortBy } from "lodash/fp";
Expand All @@ -29,42 +27,42 @@ const selectionSet = [
"activities.noteBlocks.todo.todo",
"activities.noteBlocks.todo.status",
"activities.noteBlocks.todo.doneOn",
"activities.noteBlocks.updatedAt",
"activities.noteBlocks.todo.updatedAt",
] as const;

type MeetingTodoData = SelectionSet<
Schema["Meeting"]["type"],
typeof selectionSet
>;
type TodoData = MeetingTodoData["activities"][number]["noteBlocks"][number];

const mapMeetingTodo = ({
id: meetingId,
activities,
}: MeetingTodoData): MeetingTodo[] =>
const mapMeetingTodo = (meeting: MeetingTodoData): MeetingTodo[] =>
flow(
identity<typeof activities>,
identity<typeof meeting>,
get("activities"),
flatMap(({ id: activityId, noteBlocks, noteBlockIds, forProjects }) =>
flow(
filter(get("todo")),
filter(isNotNil),
filter(notAnOrphan({ noteBlockIds, noteBlocks })),
flatMap(
({ id: blockId, todo, updatedAt }: TodoData): MeetingTodo => ({
meetingId,
identity<typeof noteBlocks>,
filter((block) =>
noteBlocks.some(
(b) => noteBlockIds?.includes(b.id) && b.todo.id === block.todo.id
)
),
map(
({ id: blockId, todo }): MeetingTodo => ({
meetingId: meeting.id,
todoId: getTodoId(todo),
todo: getTodoJson(todo),
done: getTodoStatus(todo),
doneOn: getTodoDoneOn(todo),
activityId,
blockId,
projectIds: map("projectsId")(forProjects),
updatedAt: new Date(updatedAt),
updatedAt: new Date(todo.updatedAt),
})
)
)(noteBlocks)
)
)(activities);
)(meeting);

const fetchMeetingTodos = (meetingId: string | undefined) => async () => {
if (!meetingId) return [];
Expand All @@ -78,7 +76,11 @@ const fetchMeetingTodos = (meetingId: string | undefined) => async () => {
if (!data) throw new Error("fetchMeetingTodos didn't retrieve data");

try {
return flow(mapMeetingTodo, sortBy(getTodoOrder<MeetingTodo>))(data);
return flow(
identity<typeof data>,
mapMeetingTodo,
sortBy(getTodoOrder<MeetingTodo>)
)(data);
} catch (error) {
console.error("fetchMeetingTodos", { error });
throw error;
Expand All @@ -89,8 +91,9 @@ const useMeetingTodos = (meetingId: string | undefined) => {
data: meetingTodos,
isLoading,
error,
mutate,
} = useSWR(`/meetings/${meetingId}/todos`, fetchMeetingTodos(meetingId));
return { meetingTodos, isLoading, error };
return { meetingTodos, isLoading, error, mutate };
};

export default useMeetingTodos;
29 changes: 23 additions & 6 deletions components/meetings/meeting-next-actions.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,40 @@
import { MeetingTodo } from "@/api/useMeetingTodos";
import { Todo } from "@/api/useProjectTodos";
import { FC } from "react";
import DefaultAccordionItem from "../ui-elements/accordion/DefaultAccordionItem";
import { getTodoText } from "../ui-elements/editors/helpers/text-generation";
import TodoViewer from "../ui-elements/editors/todo-viewer/TodoViewer";
import NextAction from "../ui-elements/project-details/next-action";

type MeetingNextActionsProps = {
todos: Todo[] | undefined;
todos: MeetingTodo[] | undefined;
mutate: (todo: MeetingTodo[], refresh: boolean) => void;
};

const MeetingNextActions: FC<MeetingNextActionsProps> = ({ todos }) =>
todos &&
todos.length > 0 && (
const MeetingNextActions: FC<MeetingNextActionsProps> = ({ todos, mutate }) => {
const mutateTodo = (todo: Todo, refresh: boolean) =>
todos &&
mutate(
todos.map((t) => (t.todoId !== todo.todoId ? t : { ...t, ...todo })),
refresh
);

return (
<DefaultAccordionItem
value="next-actions"
triggerTitle="Agreed Next Actions"
triggerSubTitle={getTodoText(todos)}
>
<TodoViewer todos={todos} />
<div className="space-y-2">
{todos && todos.length === 0 ? (
<div className="text-sm text-muted-foreground">No next actions</div>
) : (
todos?.map((todo) => (
<NextAction key={todo.todoId} {...{ todo, mutate: mutateTodo }} />
))
)}
</div>
</DefaultAccordionItem>
);
};

export default MeetingNextActions;
4 changes: 2 additions & 2 deletions components/meetings/meeting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ const MeetingRecord: FC<MeetingRecordProps> = ({
const [meetingDate, setMeetingDate] = useState(
meeting?.meetingOn || new Date()
);
const { meetingTodos } = useMeetingTodos(meeting?.id);
const { meetingTodos, mutate } = useMeetingTodos(meeting?.id);

useEffect(() => {
if (!meeting) return;
Expand Down Expand Up @@ -203,7 +203,7 @@ const MeetingRecord: FC<MeetingRecordProps> = ({
removeParticipant={removeMeetingParticipant}
/>

<MeetingNextActions todos={meetingTodos} />
<MeetingNextActions todos={meetingTodos} mutate={mutate} />

<MeetingActivityList meeting={meeting} />
</>
Expand Down
13 changes: 8 additions & 5 deletions components/planning/day/DailyPlanProjectMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,17 @@ const DailyPlanProjectMenu: FC<DailyPlanProjectMenuProps> = ({
});

return (
<div className="flex flex-row gap-1">
<div className="flex flex-wrap gap-1">
<ButtonPlayPause
state={dailyPlanProject.maybe ? "PAUSE" : "PLAY"}
className="w-7 h-7 p-1"
className="h-7 p-1"
onClick={() => addProject(!dailyPlanProject.maybe)}
/>

<IconButton
tooltip="Add todo…"
className="w-7 h-7 p-1"
label={isTodoFormOpen ? "Close" : "Add Todo"}
className="w-24 h-7 p-1"
onClick={() => setIsTodoFormOpen((val) => !val)}
>
<Plus
Expand All @@ -65,7 +66,8 @@ const DailyPlanProjectMenu: FC<DailyPlanProjectMenuProps> = ({

<IconButton
tooltip={showTodos ? "Hide todos" : "Show todos"}
className="w-7 h-7 p-1"
className="w-28 h-7 p-1"
label={showTodos ? "Hide todos" : "Show todos"}
onClick={() => setShowTodos((val) => !val)}
>
<Square className={cn(!showTodos && "text-gray-300")} />
Expand All @@ -75,7 +77,8 @@ const DailyPlanProjectMenu: FC<DailyPlanProjectMenuProps> = ({
tooltip={
showDonePostPoned ? "Hide done & postponed" : "Show done & postponed"
}
className="w-7 h-7 p-1"
label={showDonePostPoned ? "Hide inactive" : "Show inactive"}
className="w-32 h-7 p-1"
onClick={() => setShowDonePostPoned((val) => !val)}
>
<Check className={cn(!showDonePostPoned && "text-gray-300")} />
Expand Down
14 changes: 3 additions & 11 deletions components/planning/day/DailyPlanProjectTodoMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { ProjectTodo } from "@/api/useProjectTodos";
import { cn } from "@/lib/utils";
import { ArrowUpRightFromSquare } from "lucide-react";
import Link from "next/link";
import NextActionDetailBtn from "@/components/ui-elements/project-details/next-action-detail-btn";
import { FC } from "react";
import PostPoneBtn from "./postpone-btn";

Expand All @@ -18,19 +16,13 @@ const DailyPlanProjectTodoMenu: FC<DailyPlanProjectTodoMenuProps> = ({
mutate,
status,
}) => {
const iconStyle = "w-4 h-4 text-muted-foreground";

return (
<div className="flex flex-row gap-1">
<div className="flex flex-wrap gap-2">
{status !== "DONE" && (
<PostPoneBtn {...{ dayPlanId, todo, status, mutate }} />
)}

<Link href={`/activities/${todo.activityId}`} target="_blank">
<ArrowUpRightFromSquare
className={cn(iconStyle, "hover:text-primary")}
/>
</Link>
<NextActionDetailBtn {...{ todo }} />
</div>
);
};
Expand Down
22 changes: 14 additions & 8 deletions components/planning/day/postpone-btn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,25 @@ const PostPoneBtn: FC<PostPoneBtnProps> = ({
const CalenderIcon = status === "POSTPONED" ? CalendarOff : Calendar;
const CalenderIconHover = status !== "POSTPONED" ? CalendarOff : Calendar;

const label = status === "POSTPONED" ? "Activate todo" : "Pause todo";

const iconSize = "w-4 h-4";

return isPostponing ? (
<Loader2 className={cn(iconSize, "text-muted-foreground animate-spin")} />
) : (
<div className="cursor-pointer hover:children:hidden group">
<CalenderIcon
className={cn(iconSize, "text-muted-foreground group-hover:hidden")}
/>
<CalenderIconHover
className={cn(iconSize, "text-primary hidden group-hover:block")}
onClick={handlePostPone(status !== "POSTPONED")}
/>
<div className="w-28 cursor-pointer hover:children:hidden group">
<div className="flex flex-row gap-1 text-muted-foreground group-hover:hidden">
<CalenderIcon className={iconSize} />
<span className="text-sm -translate-y-0.5">{label}</span>
</div>
<div className="flex-row gap-1 text-primary hidden group-hover:flex">
<CalenderIconHover
className={iconSize}
onClick={handlePostPone(status !== "POSTPONED")}
/>
<span className="text-sm -translate-y-0.5">{label}</span>
</div>
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,12 @@ const AddTodoSection: FC<AddTodoSectionProps> = ({
return (
<div className={cn("space-y-2", className)}>
{!formControl && (
<Button variant="outline" size="sm" onClick={onOpenChange}>
<Button
variant="outline"
size="sm"
onClick={onOpenChange}
className="w-40 text-muted-foreground hover:text-primary"
>
{!showTodoEditor ? (
<PlusCircle className="w-4 h-4 mr-1" />
) : (
Expand Down
29 changes: 29 additions & 0 deletions components/ui-elements/project-details/next-action-detail-btn.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Todo } from "@/api/useProjectTodos";
import { cn } from "@/lib/utils";
import { ArrowUpRightFromSquare } from "lucide-react";
import Link from "next/link";
import { FC } from "react";

interface NextActionDetailBtnProps {
todo: Todo;
className?: string;
}

const NextActionDetailBtn: FC<NextActionDetailBtnProps> = ({
todo,
className,
}) => (
<Link
href={`/activities/${todo.activityId}`}
target="_blank"
className={cn(
"flex flex-row items-baseline gap-1 text-muted-foreground hover:text-primary",
className
)}
>
<ArrowUpRightFromSquare className="w-4 h-4" />
<span className="text-sm -translate-y-0.5">Details</span>
</Link>
);

export default NextActionDetailBtn;
38 changes: 38 additions & 0 deletions components/ui-elements/project-details/next-action.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { finishTodo } from "@/api/todos/finish-todo";
import { Todo } from "@/api/useProjectTodos";
import { Checkbox } from "@/components/ui/checkbox";
import { FC } from "react";
import SimpleReadOnly from "../editors/simple-visualizer/SimpleReadOnly";
import NextActionDetailBtn from "./next-action-detail-btn";

interface NextActionProps {
todo: Todo;
mutate: (todo: Todo, refresh: boolean) => void;
}

const NextAction: FC<NextActionProps> = ({ todo, mutate }) => {
const onFinish = () =>
finishTodo({
data: { todoId: todo.todoId, done: !todo.done },
options: {
mutate: (refresh) => mutate({ ...todo, done: !todo.done }, refresh),
},
});

return (
<div className="flex flex-row gap-1 items-start">
<>
<div className="w-6 min-w-6 mt-[0.8rem]">
<Checkbox checked={todo.done} onCheckedChange={onFinish} />
</div>

<div className="flex-1">
<SimpleReadOnly content={todo.todo.content || []} />
<NextActionDetailBtn todo={todo} />
</div>
</>
</div>
);
};

export default NextAction;
Loading

0 comments on commit e781556

Please sign in to comment.