From f9986cd8f076e5ccaca728a0f3ca6fa46e854090 Mon Sep 17 00:00:00 2001 From: kaulfield23 Date: Mon, 25 Sep 2023 12:02:53 +0200 Subject: [PATCH 1/5] create useTask hook and replace model to hook --- .../items/TaskOverviewListItem.tsx | 9 ++-- .../ActivityList/items/TaskListItem.tsx | 11 +++-- src/features/tasks/hooks/useTask.ts | 42 +++++++++++++++++++ 3 files changed, 50 insertions(+), 12 deletions(-) create mode 100644 src/features/tasks/hooks/useTask.ts diff --git a/src/features/campaigns/components/ActivitiesOverview/items/TaskOverviewListItem.tsx b/src/features/campaigns/components/ActivitiesOverview/items/TaskOverviewListItem.tsx index 1cd9ad5c5b..460c96975e 100644 --- a/src/features/campaigns/components/ActivitiesOverview/items/TaskOverviewListItem.tsx +++ b/src/features/campaigns/components/ActivitiesOverview/items/TaskOverviewListItem.tsx @@ -3,8 +3,7 @@ import { CheckBoxOutlined, People } from '@mui/icons-material'; import OverviewListItem from './OverviewListItem'; import { TaskActivity } from 'features/campaigns/models/CampaignActivitiesModel'; -import TaskModel from 'features/tasks/models/TaskModel'; -import useModel from 'core/useModel'; +import useTask from 'features/tasks/hooks/useTask'; import ZUIStackedStatusBar from 'zui/ZUIStackedStatusBar'; interface TasksOverviewListItemProps { @@ -17,11 +16,9 @@ const TaskOverviewListItem: FC = ({ focusDate, }) => { const task = activity.data; - const model = useModel( - (env) => new TaskModel(env, task.organization.id, task.id) - ); + const { getTaskStats } = useTask(task.organization.id, task.id); - const stats = model.getTaskStats().data; + const stats = getTaskStats().data; return ( { - const model = useModel((env) => new TaskModel(env, orgId, taskId)); - const task = model.getTask().data; - const stats = model.getTaskStats().data; + const { getTask, getTaskStats } = useTask(orgId, taskId); + const task = getTask().data; + const stats = getTaskStats().data; if (!task) { return null; @@ -31,7 +30,7 @@ const TaskListItem = ({ orgId, taskId }: TaskListItemProps) => { color = STATUS_COLORS.BLUE; } - const statsLoading = model.getTaskStats().isLoading; + const statsLoading = getTaskStats().isLoading; return ( IFuture; + getTaskStats: () => IFuture; +} + +export default function useTask(orgId: number, taskId: number): UseTaskReturn { + const apiClient = useApiClient(); + + const state = useSelector((state: RootState) => state); + const env = useEnv(); + + const getTask = (): IFuture => { + const item = state.tasks.tasksList.items.find((item) => item.id === taskId); + + return loadItemIfNecessary(item, env.store, { + actionOnLoad: () => taskLoad(taskId), + actionOnSuccess: (data) => taskLoaded(data), + loader: () => + apiClient.get(`/api/orgs/${orgId}/tasks/${taskId}`), + }); + }; + + const getTaskStats = () => { + const item = state.tasks.statsById[taskId]; + return loadItemIfNecessary(item, env.store, { + actionOnLoad: () => statsLoad(taskId), + actionOnSuccess: (data) => statsLoaded([taskId, data]), + loader: () => apiClient.rpc(getStats, { orgId, taskId }), + }); + }; + return { getTask, getTaskStats }; +} From 3c05924af05e0b3c4ae7094e166fb6abe12bacf3 Mon Sep 17 00:00:00 2001 From: kaulfield23 Date: Mon, 25 Sep 2023 12:07:25 +0200 Subject: [PATCH 2/5] fix lint --- .../components/ActivityList/items/TaskListItem.tsx | 2 +- src/features/tasks/hooks/useTask.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/features/campaigns/components/ActivityList/items/TaskListItem.tsx b/src/features/campaigns/components/ActivityList/items/TaskListItem.tsx index b6a9f75a1d..c852b4b145 100644 --- a/src/features/campaigns/components/ActivityList/items/TaskListItem.tsx +++ b/src/features/campaigns/components/ActivityList/items/TaskListItem.tsx @@ -2,8 +2,8 @@ import { CheckBoxOutlined, People } from '@mui/icons-material'; import ActivityListItemWithStats from './ActivityListItemWithStats'; import { STATUS_COLORS } from './ActivityListItem'; -import getTaskStatus, { TASK_STATUS } from 'features/tasks/utils/getTaskStatus'; import useTask from 'features/tasks/hooks/useTask'; +import getTaskStatus, { TASK_STATUS } from 'features/tasks/utils/getTaskStatus'; interface TaskListItemProps { orgId: number; diff --git a/src/features/tasks/hooks/useTask.ts b/src/features/tasks/hooks/useTask.ts index 4f5982173f..3b1276ed1e 100644 --- a/src/features/tasks/hooks/useTask.ts +++ b/src/features/tasks/hooks/useTask.ts @@ -1,12 +1,12 @@ import { useSelector } from 'react-redux'; -import { loadItemIfNecessary } from 'core/caching/cacheUtils'; import { IFuture } from 'core/caching/futures'; +import { loadItemIfNecessary } from 'core/caching/cacheUtils'; import { RootState } from 'core/store'; -import { useApiClient, useEnv } from 'core/hooks'; import { ZetkinTask } from '../components/types'; import getStats, { TaskStats } from '../rpc/getTaskStats'; -import { taskLoad, taskLoaded, statsLoad, statsLoaded } from '../store'; +import { statsLoad, statsLoaded, taskLoad, taskLoaded } from '../store'; +import { useApiClient, useEnv } from 'core/hooks'; interface UseTaskReturn { getTask: () => IFuture; @@ -15,9 +15,9 @@ interface UseTaskReturn { export default function useTask(orgId: number, taskId: number): UseTaskReturn { const apiClient = useApiClient(); + const env = useEnv(); const state = useSelector((state: RootState) => state); - const env = useEnv(); const getTask = (): IFuture => { const item = state.tasks.tasksList.items.find((item) => item.id === taskId); From c2c8aaade9d40dccf841070ab6fc75e9f6c3e23e Mon Sep 17 00:00:00 2001 From: kaulfield23 Date: Mon, 25 Sep 2023 12:08:18 +0200 Subject: [PATCH 3/5] delete taskModel --- src/features/tasks/models/TaskModel.ts | 27 -------------------------- 1 file changed, 27 deletions(-) delete mode 100644 src/features/tasks/models/TaskModel.ts diff --git a/src/features/tasks/models/TaskModel.ts b/src/features/tasks/models/TaskModel.ts deleted file mode 100644 index af9dad13b4..0000000000 --- a/src/features/tasks/models/TaskModel.ts +++ /dev/null @@ -1,27 +0,0 @@ -import Environment from 'core/env/Environment'; -import { IFuture } from 'core/caching/futures'; -import { ModelBase } from 'core/models'; -import TasksRepo from '../repos/TasksRepo'; -import { TaskStats } from '../rpc/getTaskStats'; -import { ZetkinTask } from '../components/types'; - -export default class TaskModel extends ModelBase { - private _orgId: number; - private _repo: TasksRepo; - private _taskId: number; - - constructor(env: Environment, orgId: number, taskId: number) { - super(); - this._orgId = orgId; - this._repo = new TasksRepo(env); - this._taskId = taskId; - } - - getTask(): IFuture { - return this._repo.getTask(this._orgId, this._taskId); - } - - getTaskStats(): IFuture { - return this._repo.getTaskStats(this._orgId, this._taskId); - } -} From 682c92a8f02285be8a931b6407858fb32dfb739b Mon Sep 17 00:00:00 2001 From: kaulfield23 Date: Mon, 25 Sep 2023 14:50:08 +0200 Subject: [PATCH 4/5] fix lint, change return properties --- .../items/TaskOverviewListItem.tsx | 4 +- .../ActivityList/items/TaskListItem.tsx | 10 ++-- src/features/tasks/hooks/useTask.ts | 56 ++++++++++++++----- 3 files changed, 48 insertions(+), 22 deletions(-) diff --git a/src/features/campaigns/components/ActivitiesOverview/items/TaskOverviewListItem.tsx b/src/features/campaigns/components/ActivitiesOverview/items/TaskOverviewListItem.tsx index 460c96975e..94ecb9ebd7 100644 --- a/src/features/campaigns/components/ActivitiesOverview/items/TaskOverviewListItem.tsx +++ b/src/features/campaigns/components/ActivitiesOverview/items/TaskOverviewListItem.tsx @@ -16,9 +16,7 @@ const TaskOverviewListItem: FC = ({ focusDate, }) => { const task = activity.data; - const { getTaskStats } = useTask(task.organization.id, task.id); - - const stats = getTaskStats().data; + const { taskStats: stats } = useTask(task.organization.id, task.id); return ( { - const { getTask, getTaskStats } = useTask(orgId, taskId); - const task = getTask().data; - const stats = getTaskStats().data; + const { + taskData: task, + taskStats: stats, + taskStatsIsLoading: statsLoading, + } = useTask(orgId, taskId); if (!task) { return null; @@ -30,8 +32,6 @@ const TaskListItem = ({ orgId, taskId }: TaskListItemProps) => { color = STATUS_COLORS.BLUE; } - const statsLoading = getTaskStats().isLoading; - return ( IFuture; - getTaskStats: () => IFuture; + taskData: ZetkinTask | null; + tasksData: ZetkinTask[] | null; + tasksDataLoading: boolean; + taskStats: TaskStats | null; + taskStatsIsLoading: boolean; } -export default function useTask(orgId: number, taskId: number): UseTaskReturn { +export default function useTask(orgId: number, taskId?: number): UseTaskReturn { const apiClient = useApiClient(); const env = useEnv(); - const state = useSelector((state: RootState) => state); + const tasks = useSelector((state: RootState) => state.tasks); - const getTask = (): IFuture => { - const item = state.tasks.tasksList.items.find((item) => item.id === taskId); + const getTask = (taskId: number) => { + const item = tasks.tasksList.items.find((item) => item.id === taskId); return loadItemIfNecessary(item, env.store, { actionOnLoad: () => taskLoad(taskId), @@ -30,13 +42,29 @@ export default function useTask(orgId: number, taskId: number): UseTaskReturn { }); }; - const getTaskStats = () => { - const item = state.tasks.statsById[taskId]; + const getTasks = () => { + const taskList = tasks.tasksList; + return loadListIfNecessary(taskList, env.store, { + actionOnLoad: () => tasksLoad(), + actionOnSuccess: (data) => tasksLoaded(data), + loader: () => apiClient.get(`/api/orgs/${orgId}/tasks/`), + }); + }; + + const getTaskStats = (taskId: number) => { + const item = tasks.statsById[taskId!]; return loadItemIfNecessary(item, env.store, { - actionOnLoad: () => statsLoad(taskId), - actionOnSuccess: (data) => statsLoaded([taskId, data]), + actionOnLoad: () => statsLoad(taskId!), + actionOnSuccess: (data) => statsLoaded([taskId!, data]), loader: () => apiClient.rpc(getStats, { orgId, taskId }), }); }; - return { getTask, getTaskStats }; + + return { + taskData: getTask(taskId!).data, + taskStats: getTaskStats(taskId!).data, + taskStatsIsLoading: getTaskStats(taskId!).isLoading, + tasksData: getTasks().data, + tasksDataLoading: getTasks().isLoading, + }; } From ffabc3764298b4851f54d1a7093ca7bdd4a3082a Mon Sep 17 00:00:00 2001 From: kaulfield23 Date: Thu, 28 Sep 2023 11:41:10 +0200 Subject: [PATCH 5/5] split into several hooks --- .../items/TaskOverviewListItem.tsx | 4 +- .../ActivityList/items/TaskListItem.tsx | 8 +-- src/features/tasks/hooks/useTask.ts | 64 ++++--------------- src/features/tasks/hooks/useTaskStats.ts | 34 ++++++++++ src/features/tasks/hooks/useTasks.ts | 28 ++++++++ src/features/tasks/repos/TasksRepo.ts | 37 +---------- 6 files changed, 81 insertions(+), 94 deletions(-) create mode 100644 src/features/tasks/hooks/useTaskStats.ts create mode 100644 src/features/tasks/hooks/useTasks.ts diff --git a/src/features/campaigns/components/ActivitiesOverview/items/TaskOverviewListItem.tsx b/src/features/campaigns/components/ActivitiesOverview/items/TaskOverviewListItem.tsx index 94ecb9ebd7..a911c5a854 100644 --- a/src/features/campaigns/components/ActivitiesOverview/items/TaskOverviewListItem.tsx +++ b/src/features/campaigns/components/ActivitiesOverview/items/TaskOverviewListItem.tsx @@ -3,7 +3,7 @@ import { CheckBoxOutlined, People } from '@mui/icons-material'; import OverviewListItem from './OverviewListItem'; import { TaskActivity } from 'features/campaigns/models/CampaignActivitiesModel'; -import useTask from 'features/tasks/hooks/useTask'; +import useTaskStats from 'features/tasks/hooks/useTaskStats'; import ZUIStackedStatusBar from 'zui/ZUIStackedStatusBar'; interface TasksOverviewListItemProps { @@ -16,7 +16,7 @@ const TaskOverviewListItem: FC = ({ focusDate, }) => { const task = activity.data; - const { taskStats: stats } = useTask(task.organization.id, task.id); + const { data: stats } = useTaskStats(task.organization.id, task.id); return ( { - const { - taskData: task, - taskStats: stats, - taskStatsIsLoading: statsLoading, - } = useTask(orgId, taskId); + const { data: task } = useTask(orgId, taskId); + const { data: stats, isLoading: statsLoading } = useTaskStats(orgId, taskId); if (!task) { return null; diff --git a/src/features/tasks/hooks/useTask.ts b/src/features/tasks/hooks/useTask.ts index 0bab296644..45629828b7 100644 --- a/src/features/tasks/hooks/useTask.ts +++ b/src/features/tasks/hooks/useTask.ts @@ -1,70 +1,30 @@ import { useSelector } from 'react-redux'; +import { loadItemIfNecessary } from 'core/caching/cacheUtils'; import { RootState } from 'core/store'; import { ZetkinTask } from '../components/types'; -import getStats, { TaskStats } from '../rpc/getTaskStats'; -import { - loadItemIfNecessary, - loadListIfNecessary, -} from 'core/caching/cacheUtils'; -import { - statsLoad, - statsLoaded, - taskLoad, - taskLoaded, - tasksLoad, - tasksLoaded, -} from '../store'; +import { taskLoad, taskLoaded } from '../store'; import { useApiClient, useEnv } from 'core/hooks'; interface UseTaskReturn { - taskData: ZetkinTask | null; - tasksData: ZetkinTask[] | null; - tasksDataLoading: boolean; - taskStats: TaskStats | null; - taskStatsIsLoading: boolean; + data: ZetkinTask | null; } -export default function useTask(orgId: number, taskId?: number): UseTaskReturn { +export default function useTask(orgId: number, taskId: number): UseTaskReturn { const apiClient = useApiClient(); const env = useEnv(); - const tasks = useSelector((state: RootState) => state.tasks); - const getTask = (taskId: number) => { - const item = tasks.tasksList.items.find((item) => item.id === taskId); - - return loadItemIfNecessary(item, env.store, { - actionOnLoad: () => taskLoad(taskId), - actionOnSuccess: (data) => taskLoaded(data), - loader: () => - apiClient.get(`/api/orgs/${orgId}/tasks/${taskId}`), - }); - }; - - const getTasks = () => { - const taskList = tasks.tasksList; - return loadListIfNecessary(taskList, env.store, { - actionOnLoad: () => tasksLoad(), - actionOnSuccess: (data) => tasksLoaded(data), - loader: () => apiClient.get(`/api/orgs/${orgId}/tasks/`), - }); - }; + const item = tasks.tasksList.items.find((item) => item.id === taskId); - const getTaskStats = (taskId: number) => { - const item = tasks.statsById[taskId!]; - return loadItemIfNecessary(item, env.store, { - actionOnLoad: () => statsLoad(taskId!), - actionOnSuccess: (data) => statsLoaded([taskId!, data]), - loader: () => apiClient.rpc(getStats, { orgId, taskId }), - }); - }; + const taskFuture = loadItemIfNecessary(item, env.store, { + actionOnLoad: () => taskLoad(taskId), + actionOnSuccess: (data) => taskLoaded(data), + loader: () => + apiClient.get(`/api/orgs/${orgId}/tasks/${taskId}`), + }); return { - taskData: getTask(taskId!).data, - taskStats: getTaskStats(taskId!).data, - taskStatsIsLoading: getTaskStats(taskId!).isLoading, - tasksData: getTasks().data, - tasksDataLoading: getTasks().isLoading, + data: taskFuture.data, }; } diff --git a/src/features/tasks/hooks/useTaskStats.ts b/src/features/tasks/hooks/useTaskStats.ts new file mode 100644 index 0000000000..2e9d495034 --- /dev/null +++ b/src/features/tasks/hooks/useTaskStats.ts @@ -0,0 +1,34 @@ +import { useSelector } from 'react-redux'; + +import { loadItemIfNecessary } from 'core/caching/cacheUtils'; +import { RootState } from 'core/store'; +import getStats, { TaskStats } from '../rpc/getTaskStats'; +import { statsLoad, statsLoaded } from '../store'; +import { useApiClient, useEnv } from 'core/hooks'; + +interface UseTaskStatsReturn { + data: TaskStats | null; + isLoading: boolean; +} + +export default function useTaskStats( + orgId: number, + taskId: number +): UseTaskStatsReturn { + const apiClient = useApiClient(); + const env = useEnv(); + const tasks = useSelector((state: RootState) => state.tasks); + + const item = tasks.statsById[taskId!]; + + const taskStatsFuture = loadItemIfNecessary(item, env.store, { + actionOnLoad: () => statsLoad(taskId!), + actionOnSuccess: (data) => statsLoaded([taskId!, data]), + loader: () => apiClient.rpc(getStats, { orgId, taskId }), + }); + + return { + data: taskStatsFuture.data, + isLoading: taskStatsFuture.isLoading, + }; +} diff --git a/src/features/tasks/hooks/useTasks.ts b/src/features/tasks/hooks/useTasks.ts new file mode 100644 index 0000000000..cf338f9bef --- /dev/null +++ b/src/features/tasks/hooks/useTasks.ts @@ -0,0 +1,28 @@ +import { useSelector } from 'react-redux'; + +import { IFuture } from 'core/caching/futures'; +import { loadListIfNecessary } from 'core/caching/cacheUtils'; +import { RootState } from 'core/store'; +import { ZetkinTask } from '../components/types'; +import { tasksLoad, tasksLoaded } from '../store'; +import { useApiClient, useEnv } from 'core/hooks'; + +interface UseTasksReturn { + tasksFuture: IFuture; +} + +export default function useTasks(orgId: number): UseTasksReturn { + const taskList = useSelector((state: RootState) => state.tasks.tasksList); + const env = useEnv(); + const apiClient = useApiClient(); + + const tasksFuture = loadListIfNecessary(taskList, env.store, { + actionOnLoad: () => tasksLoad(), + actionOnSuccess: (data) => tasksLoaded(data), + loader: () => apiClient.get(`/api/orgs/${orgId}/tasks/`), + }); + + return { + tasksFuture, + }; +} diff --git a/src/features/tasks/repos/TasksRepo.ts b/src/features/tasks/repos/TasksRepo.ts index f8b8c02f78..275b360fd1 100644 --- a/src/features/tasks/repos/TasksRepo.ts +++ b/src/features/tasks/repos/TasksRepo.ts @@ -1,21 +1,10 @@ import Environment from 'core/env/Environment'; import IApiClient from 'core/api/client/IApiClient'; import { IFuture } from 'core/caching/futures'; +import { loadListIfNecessary } from 'core/caching/cacheUtils'; import { Store } from 'core/store'; import { ZetkinTask } from '../components/types'; -import getStats, { TaskStats } from '../rpc/getTaskStats'; -import { - loadItemIfNecessary, - loadListIfNecessary, -} from 'core/caching/cacheUtils'; -import { - statsLoad, - statsLoaded, - taskLoad, - taskLoaded, - tasksLoad, - tasksLoaded, -} from '../store'; +import { tasksLoad, tasksLoaded } from '../store'; export default class TasksRepo { private _apiClient: IApiClient; @@ -26,28 +15,6 @@ export default class TasksRepo { this._store = env.store; } - getTask(orgId: number, taskId: number): IFuture { - const state = this._store.getState(); - const item = state.tasks.tasksList.items.find((item) => item.id === taskId); - - return loadItemIfNecessary(item, this._store, { - actionOnLoad: () => taskLoad(taskId), - actionOnSuccess: (data) => taskLoaded(data), - loader: () => - this._apiClient.get(`/api/orgs/${orgId}/tasks/${taskId}`), - }); - } - - getTaskStats(orgId: number, taskId: number): IFuture { - const state = this._store.getState(); - const item = state.tasks.statsById[taskId]; - return loadItemIfNecessary(item, this._store, { - actionOnLoad: () => statsLoad(taskId), - actionOnSuccess: (data) => statsLoaded([taskId, data]), - loader: () => this._apiClient.rpc(getStats, { orgId, taskId }), - }); - } - getTasks(orgId: number): IFuture { const state = this._store.getState(); const taskList = state.tasks.tasksList;