Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TACT-347: Goal setting rework: Archived Goals Page #711

Merged
merged 9 commits into from
Apr 16, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions components/pages/Goals/ArchivePage/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';
import { observer } from 'mobx-react-lite';
import { GoalsArchiveStoreProvider } from './store';
import { GoalsArchiveView } from './view';

const GoalsArchivePage = observer(function GoalsArchivePage() {
return (
<GoalsArchiveStoreProvider>
<GoalsArchiveView />
</GoalsArchiveStoreProvider>
);
});

export default GoalsArchivePage;
40 changes: 40 additions & 0 deletions components/pages/Goals/ArchivePage/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { computed, makeObservable } from 'mobx';
import { RootStore } from '../../../../stores/RootStore';
import { getProvider } from '../../../../helpers/StoreProvider';
import { BaseGoalsStore } from "../stores/BaseGoalsStore";
import { GoalDataExtended } from "../types";

export class GoalsArchiveStore extends BaseGoalsStore {
constructor(public root: RootStore) {
super(root);

makeObservable(this, {
list: computed,
hasGoals: computed,
})
}

get list() {
return Object.entries(this.extendedGoals).reduce((acc, [id, goals]) => {
const archivedGoals = goals.filter((goal) => goal.isArchived);

if (archivedGoals.length) {
return {
...acc,
[id]: archivedGoals,
};
}

return acc;
}, {} as Record<string, GoalDataExtended[]>)
}

get hasGoals() {
return Boolean(Object.keys(this.list).length);
}
}

export const {
StoreProvider: GoalsArchiveStoreProvider,
useStore: useGoalsArchiveStore
} = getProvider(GoalsArchiveStore);
77 changes: 77 additions & 0 deletions components/pages/Goals/ArchivePage/view.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import React, { useEffect } from 'react';
import { observer } from 'mobx-react-lite';
import Head from 'next/head';
import { GoalList } from '../components/GoalList';
import { ModalsSwitcher } from '../../../../helpers/ModalsController';
import { useGoalsArchiveStore } from './store';
import { Box, Button, Heading, Text } from "@chakra-ui/react";
import NextLink from "next/link";
import { faAngleLeft } from "@fortawesome/pro-light-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useRouter } from "next/navigation";

export const GoalsArchiveView = observer(function GoalsArchiveView() {
const { push } = useRouter();
const store = useGoalsArchiveStore();

useEffect(() => {
if (!store.hasGoals) {
push('/goals');
}
}, [store.hasGoals, push]);

return (
<>
<Head>
<title>Goals Archive</title>
</Head>
{store.hasGoals && (
<>
<Box pl={32} pr={32} position='relative'>
<Heading
size='md'
fontSize='2xl'
mt={0}
mb={0}
pt={4}
pb={10}
textAlign='center'
>
Archive
</Heading>
<NextLink href='/goals' passHref>
<Button
as='a'
variant='ghost'
size='sm'
pl={1.5}
pr={1.5}
position='absolute'
top={4}
left={32}
color='gray.500'
>
<FontAwesomeIcon
fontSize={20}
icon={faAngleLeft}
fixedWidth
/>
<Text fontSize='sm' lineHeight={3} fontWeight='normal' ml={1}>
Back
</Text>
</Button>
</NextLink>
<GoalList
listBySpaces={store.list}
onDeleteGoal={store.deleteGoal}
onOpenGoal={store.editGoal}
onUpdateGoal={store.updateGoal}
onWontDo={store.wontDoSubmitModalOpen}
/>
</Box>
<ModalsSwitcher controller={store.modals} />
</>
)}
</>
);
});
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import React from 'react';
import { observer } from 'mobx-react-lite';
import { GoalsStoreProvider } from './store';
import { GoalsView } from './view';
Expand Down
80 changes: 80 additions & 0 deletions components/pages/Goals/MainPage/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { action, computed, makeObservable, observable } from 'mobx';
import { RootStore } from '../../../../stores/RootStore';
import { getProvider } from '../../../../helpers/StoreProvider';
import { BaseGoalsStore, GoalsModalsTypes } from "../stores/BaseGoalsStore";
import { GoalDataExtended } from "../types";
import { CreateGoalParams } from "../../../../stores/RootStore/Resources/GoalsStore";

export class GoalsStore extends BaseGoalsStore {
keymap = {
CREATE_GOAL: ['n'],
};

hotkeysHandlers = {
CREATE_GOAL: () => {
this.startGoalCreation();
},
};

constructor(public root: RootStore) {
super(root);

makeObservable(this, {
keymap: observable,
hotkeysHandlers: observable,
list: computed,
hasGoals: computed,
cloneGoal: action.bound,
startGoalCreation: action.bound,
});
}

get list() {
return Object.entries(this.extendedGoals).reduce((acc, [id, goals]) => {
const notArchivedGoals = goals.filter((goal) => !goal.isArchived);

if (notArchivedGoals.length) {
return {
...acc,
[id]: notArchivedGoals,
};
}

return acc;
}, {} as Record<string, GoalDataExtended[]>)
}

get hasGoals() {
return Object.keys(this.list).length;
}

get hasArchivedGoals() {
return this.root.resources.goals.list.some((goal) => goal.isArchived);
}

cloneGoal = ({ customFields, ...goal }: GoalDataExtended) => {
return this.root.resources.goals.cloneGoal(goal);
}

startGoalCreation = () => {
this.modals.open({
type: GoalsModalsTypes.CREATE_OR_UPDATE_GOAL,
props: {
onSave: async ({
goal: { customFields, ...goal },
...otherParams
}: CreateGoalParams<GoalDataExtended>) => {
await this.root.resources.goals.add({ goal, ...otherParams });
await this.loadTaskList();
this.modals.close();
},
onClose: this.modals.close,
},
});
};
}

export const {
StoreProvider: GoalsStoreProvider,
useStore: useGoalsStore
} = getProvider(GoalsStore);
103 changes: 103 additions & 0 deletions components/pages/Goals/MainPage/view.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import React from 'react';
import { observer } from 'mobx-react-lite';
import Head from 'next/head';
import { GoalList } from '../components/GoalList';
import { ModalsSwitcher } from '../../../../helpers/ModalsController';
import { useGoalsStore } from './store';
import { useHotkeysHandler } from "../../../../helpers/useHotkeysHandler";
import { Box, Button, Heading, Text } from "@chakra-ui/react";
import { GoalCreateNewButton } from "../components/GoalCreateNewButton";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faPlus } from "@fortawesome/pro-regular-svg-icons";
import { EmptyGoalListMessage } from "../components/EmptyGoalListMessage/EmptyGoalListMessage";
import NextLink from 'next/link';
import { faBoxArchive } from "@fortawesome/pro-light-svg-icons";
import { Tooltip } from "../../../shared/Tooltip";

export const GoalsView = observer(function GoalsView() {
const store = useGoalsStore();

useHotkeysHandler(store.keymap, store.hotkeysHandlers, {
enabled: !store.modals.isOpen,
keyup: true,
});

return (
<>
<Head>
<title>Goals</title>
</Head>
<Box pl={32} pr={32} position='relative'>
<Heading
size='md'
fontSize='2xl'
mt={0}
mb={0}
pt={4}
pb={10}
textAlign={store.hasGoals ? 'left' : 'center'}
>
Goals
</Heading>

{store.hasArchivedGoals && (
<NextLink href='/goals/archive' passHref>
<Tooltip label='Archive' hotkey='G then A'>
<Button
as='a'
variant='ghost'
size='sm'
pl={1.5}
pr={1.5}
position='absolute'
top={4}
right={32}
color='gray.500'
>
<FontAwesomeIcon
fontSize={20}
icon={faBoxArchive}
fixedWidth
/>
<Text fontSize='sm' lineHeight={3} fontWeight='normal' ml={1}>
Archive
</Text>
</Button>
</Tooltip>
</NextLink>
)}

{store.hasGoals ? (
<GoalList
listBySpaces={store.list}
onCloneGoal={store.cloneGoal}
onDeleteGoal={store.deleteGoal}
onOpenGoal={store.editGoal}
onUpdateGoal={store.updateGoal}
onWontDo={store.wontDoSubmitModalOpen}
/>
) : (
<EmptyGoalListMessage onCreate={store.startGoalCreation} />
)}

{store.hasGoals && (
<GoalCreateNewButton
withHotkey
withTooltip
borderRadius='full'
w={12}
h={12}
position='absolute'
bottom={6}
right={6}
mb={0}
onClick={store.startGoalCreation}
>
<FontAwesomeIcon icon={faPlus} fontSize={18} />
</GoalCreateNewButton>
)}
</Box>
<ModalsSwitcher controller={store.modals} />
</>
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import { MascotGoals } from '../../../../images/illustrations/MascotGoals';
import { GoalCreateNewButton } from "../GoalCreateNewButton";
import React from "react";

export function EmptyGoalListMessage() {
type Props = {
onCreate(): void;
}

export function EmptyGoalListMessage({ onCreate }: Props) {
return (
<Flex
flexDirection='column'
Expand All @@ -27,7 +31,7 @@ export function EmptyGoalListMessage() {
help you focus and&nbsp;complete only essential things that
bring you closer to it, postponing or&nbsp;canceling the rest.
</Text>
<GoalCreateNewButton withHotkey>
<GoalCreateNewButton onClick={onCreate} withHotkey>
Create new goal 🎯
</GoalCreateNewButton>
</Flex>
Expand Down
Loading