Skip to content

Commit

Permalink
feat(mobile): task management bb-445 (#577)
Browse files Browse the repository at this point in the history
* feat: add disabled switch prop bb-445

* feat: change style bb-445

* feat: add task-background png bb-445

* feat: add task empty state, add background bb-445

* feat: add background bb-445

* feat: add export image-background component bb-445

* feat: delete unnecessary task svg bb-445

* feat: pass isDisabled to touchable-opacity bb-445

* refactor(mobile): refactored Tasks screen bb-445

* refactor(mobile): used item.id in handleKeyExtractor  bb-445

* refactor(mobile): added getActiveAndExpiredTasks helper bb-445

* refactor(mobile): updated tasks state and actions, used useFocusEffect in Tasks screen bb-445

* fix(mobile): removed unused state bb-445

---------

Co-authored-by: Tetiana Brezhynska <[email protected]>
Co-authored-by: Tetiana Brezhynska <[email protected]>
Co-authored-by: Dmitrij Revenets <[email protected]>
  • Loading branch information
4 people authored Sep 27, 2024
1 parent 9669999 commit 0a3e281
Show file tree
Hide file tree
Showing 18 changed files with 177 additions and 97 deletions.
Binary file added apps/mobile/src/assets/images/background.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions apps/mobile/src/libs/components/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export {
ActivityIndicator,
FlatList,
Image,
ImageBackground,
KeyboardAvoidingView,
Pressable,
ScrollView,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import { styles } from "./style";

type PageSwitcherProperties = {
activeTab: string;
isDisabled?: boolean;
onTabChange: (tab: string) => void;
style?: StyleProp<ViewStyle>;
tabs: string[];
};

const PageSwitcher: React.FC<PageSwitcherProperties> = ({
activeTab,
isDisabled = false,
onTabChange,
style,
tabs,
Expand All @@ -38,6 +40,7 @@ const PageSwitcher: React.FC<PageSwitcherProperties> = ({

return (
<TouchableOpacity
disabled={isDisabled}
key={tab}
onPress={handlePress(tab)}
style={[
Expand All @@ -46,6 +49,7 @@ const PageSwitcher: React.FC<PageSwitcherProperties> = ({
globalStyles.pv8,
styles.tab,
isActive ? styles.activeTab : styles.inactiveTab,
isDisabled && styles.disabledTab,
]}
>
<Text
Expand Down
3 changes: 3 additions & 0 deletions apps/mobile/src/libs/components/page-switcher/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ const styles = StyleSheet.create({
backgroundColor: BaseColor.BG_LIGHT,
borderRadius: 30,
},
disabledTab: {
opacity: 0.5,
},
inactive: {
color: BaseColor.GRAY,
},
Expand Down
1 change: 1 addition & 0 deletions apps/mobile/src/libs/components/task-card/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const styles = StyleSheet.create({
backgroundColor: BaseColor.BG_WHITE,
},
container: {
backgroundColor: BaseColor.BG_WHITE,
borderColor: BaseColor.BG_LIGHT,
borderRadius: 16,
borderWidth: 1,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { TaskStatus } from "~/libs/enums/enums";
import { type TaskDto } from "~/packages/tasks/tasks";

type ActiveAndExpiredTasks = {
activeTasks: TaskDto[];
expiredTasks: TaskDto[];
};

const MILLISECONDS_PER_MINUTE = 60_000;

const getActiveAndExpiredTasks = (tasks: TaskDto[]): ActiveAndExpiredTasks => {
const currentTimestamp = Date.now();
const expiredTasks: TaskDto[] = [];
const activeTasks: TaskDto[] = [];

for (const task of tasks) {
const { dueDate, status } = task;
const deadlineTimestamp = new Date(dueDate).getTime();
const timeToDeadline = deadlineTimestamp - currentTimestamp;

if (
timeToDeadline < MILLISECONDS_PER_MINUTE &&
status === TaskStatus.CURRENT
) {
expiredTasks.push(task);
} else {
activeTasks.push(task);
}
}

return { activeTasks, expiredTasks };
};

export { getActiveAndExpiredTasks };
1 change: 1 addition & 0 deletions apps/mobile/src/libs/helpers/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export { checkIfAndroid, checkIfIos } from "./check-platform/check-platform";
export { createRadioButtonProperties } from "./create-radio-button-properties/create-radio-button-properties.helper";
export { generateSliderGradientColors } from "./generate-slider-gradient-colors/generate-slider-gradient-colors.helper";
export { generateSliderGradientLocations } from "./generate-slider-gradient-locations/generate-slider-gradient-locations.helper";
export { getActiveAndExpiredTasks } from "./get-active-and-expired-tasks/get-active-and-expired-tasks.helper";
export { getGradientColorsForCategory } from "./get-gradient-colors-for-category/get-gradient-colors-for-category.helper";
export { getScreenWidth } from "./get-screen-width/get-screen-width.helper";
export { getSecurityInputIconName } from "./get-security-input-icon-name/get-security-input-icon-name.helper";
Expand Down
2 changes: 1 addition & 1 deletion apps/mobile/src/libs/hooks/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export { useAppDispatch } from "./use-app-dispatch/use-app-dispatch.hook";
export { useAppForm } from "./use-app-form/use-app-form.hook";
export { useAppRoute } from "./use-app-route/use-app-route.hook";
export { useAppSelector } from "./use-app-selector/use-app-selector.hook";
export { useNavigation } from "@react-navigation/native";
export { useFocusEffect, useNavigation } from "@react-navigation/native";
export { useCallback, useEffect, useMemo, useRef, useState } from "react";
export { useController as useFormController } from "react-hook-form";
export { useWindowDimensions } from "react-native";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { EmptyTasksHeader } from "./empty-tasks-header/empty-tasks-header";
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from "react";

import { Text, View } from "~/libs/components/components";
import { globalStyles } from "~/libs/styles/styles";

import { styles } from "./styles";

type Properties = {
content: string;
};

const EmptyTasksHeader: React.FC<Properties> = ({ content }) => {
return (
<View
style={[
globalStyles.flex1,
globalStyles.alignItemsCenter,
globalStyles.justifyContentCenter,
]}
>
<Text
preset="subheading"
style={[globalStyles.ph32, styles.content]}
weight="bold"
>
{content}
</Text>
</View>
);
};

export { EmptyTasksHeader };
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { StyleSheet } from "react-native";

const styles = StyleSheet.create({
content: {
textAlign: "center",
},
});

export { styles };
1 change: 0 additions & 1 deletion apps/mobile/src/screens/tasks/libs/enums/enums.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export { TaskTab } from "./task-tab.enum";
export { TasksMode } from "./tasks-mode.enum";
export { TaskStatus } from "shared";
6 changes: 0 additions & 6 deletions apps/mobile/src/screens/tasks/libs/enums/tasks-mode.enum.ts

This file was deleted.

This file was deleted.

1 change: 0 additions & 1 deletion apps/mobile/src/screens/tasks/libs/helpers/helpers.ts

This file was deleted.

10 changes: 10 additions & 0 deletions apps/mobile/src/screens/tasks/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,19 @@ import { StyleSheet } from "react-native";
import { BaseColor } from "~/libs/enums/enums";

const styles = StyleSheet.create({
background: {
backgroundColor: "rgba(255, 255, 255, 0.9)",
},
backgroundContainer: {
marginVertical: -10,
},
container: {
backgroundColor: BaseColor.BG_WHITE,
},
empty: {
opacity: 1,
textAlign: "center",
},
switch: {
maxWidth: "50%",
},
Expand Down
124 changes: 62 additions & 62 deletions apps/mobile/src/screens/tasks/tasks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import React from "react";

import {
ExpiredTasksModal,
FlatList,
ImageBackground,
LoaderWrapper,
PageSwitcher,
ScreenWrapper,
ScrollView,
TaskCard,
Text,
View,
Expand All @@ -16,82 +17,49 @@ import {
useAppSelector,
useCallback,
useEffect,
useFocusEffect,
useState,
} from "~/libs/hooks/hooks";
import { globalStyles } from "~/libs/styles/styles";
import { type ValueOf } from "~/libs/types/types";
import { type ImageSourcePropType } from "~/libs/types/types";
import { type TaskDto } from "~/packages/tasks/tasks";
import { type UserDto } from "~/packages/users/users";
import { actions as taskActions } from "~/slices/task/task";
import { actions as userActions } from "~/slices/users/users";

import { TasksMode, TaskStatus, TaskTab } from "./libs/enums/enums";
import { getMillisecondsLeft } from "./libs/helpers/helpers";
import { EmptyTasksHeader } from "./libs/components/components";
import { TaskStatus, TaskTab } from "./libs/enums/enums";
import { styles } from "./styles";

const MILLISECONDS_PER_MINUTE = 60_000;

const Tasks: React.FC = () => {
const dispatch = useAppDispatch();

const authenticatedUser = useAppSelector(({ auth }) => auth.user);
const { activeTasks, dataStatus, expiredTasks, tasks } = useAppSelector(
const { activeTasks, dataStatus, expiredTasks, pastTasks } = useAppSelector(
({ tasks }) => tasks,
);

const [mode, setMode] = useState<ValueOf<typeof TasksMode>>(
TasksMode.CURRENT,
);
const [isCurrentMode, setIsCurrentMode] = useState<boolean>(true);

const hasExpiredTasks = expiredTasks.length > NumericalValue.ZERO;
const taskbarTasks = mode === TasksMode.CURRENT ? activeTasks : tasks;
const taskbarTasks = isCurrentMode ? activeTasks : pastTasks;

useEffect(() => {
void dispatch(
userActions.getById({ id: (authenticatedUser as UserDto).id }),
);
}, [dispatch, authenticatedUser]);

useEffect(() => {
if (mode === TasksMode.CURRENT) {
useFocusEffect(
useCallback(() => {
void dispatch(taskActions.getCurrentTasks());
}

if (mode === TasksMode.PAST) {
void dispatch(taskActions.getPastTasks());
}
}, [dispatch, mode]);

useEffect(() => {
const currentTime = Date.now();
const expired: TaskDto[] = [];
const active: TaskDto[] = [];

for (const task of tasks) {
const { dueDate, status } = task;
const timeToDeadline = getMillisecondsLeft(currentTime, dueDate);

if (
timeToDeadline < MILLISECONDS_PER_MINUTE &&
status === TaskStatus.CURRENT
) {
expired.push(task);
} else {
active.push(task);
}
}

void dispatch(taskActions.setActiveTasks(active));
void dispatch(taskActions.setExpiredTasks(expired));
}, [tasks, dispatch]);
}, [dispatch]),
);

const handleModeToggle = useCallback(() => {
setMode((previousState) => {
return previousState === TasksMode.CURRENT
? TasksMode.PAST
: TasksMode.CURRENT;
});
}, []);
setIsCurrentMode(!isCurrentMode);
}, [isCurrentMode]);

const handleSkip = useCallback(
(id: number): void => {
Expand All @@ -116,6 +84,34 @@ const Tasks: React.FC = () => {
[dispatch],
);

const handleRenderTask = useCallback(
({ item }: { item: TaskDto }) => {
return (
<TaskCard
key={item.id}
onComplete={handleComplete}
onExpire={handleExpired}
onSkip={handleSkip}
task={item}
/>
);
},
[handleComplete, handleExpired, handleSkip],
);

const handleKeyExtractor = useCallback(
(item: TaskDto) => item.id.toString(),
[],
);

const emptyTasksComponent = useCallback(() => {
const content = isCurrentMode
? "You don't have any active tasks"
: "You don't have any past tasks";

return <EmptyTasksHeader content={content} />;
}, [isCurrentMode]);

return (
<ScreenWrapper edges={["top", "left", "right"]} style={styles.container}>
<LoaderWrapper isLoading={dataStatus === DataStatus.PENDING}>
Expand All @@ -136,27 +132,31 @@ const Tasks: React.FC = () => {
My Tasks
</Text>
<PageSwitcher
activeTab={
mode === TasksMode.CURRENT ? TaskTab.ACTIVE : TaskTab.PAST
}
activeTab={isCurrentMode ? TaskTab.ACTIVE : TaskTab.PAST}
onTabChange={handleModeToggle}
style={[globalStyles.flex1, styles.switch]}
tabs={[TaskTab.ACTIVE, TaskTab.PAST]}
/>
</View>
<ScrollView
contentContainerStyle={[globalStyles.gap8, globalStyles.p16]}
<ImageBackground
resizeMode="cover"
source={
require("~/assets/images/background.png") as ImageSourcePropType
}
style={[globalStyles.flex1, styles.backgroundContainer]}
>
{taskbarTasks.map((task) => (
<TaskCard
key={task.id}
onComplete={handleComplete}
onExpire={handleExpired}
onSkip={handleSkip}
task={task}
/>
))}
</ScrollView>
<FlatList
contentContainerStyle={[
globalStyles.gap8,
globalStyles.ph16,
globalStyles.pv24,
]}
data={taskbarTasks}
keyExtractor={handleKeyExtractor}
ListEmptyComponent={emptyTasksComponent}
renderItem={handleRenderTask}
/>
</ImageBackground>
</>
)}
</LoaderWrapper>
Expand Down
Loading

0 comments on commit 0a3e281

Please sign in to comment.