add: tasks func()

Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
2026-05-20 13:18:44 +08:00
parent 2b1955608d
commit 8e67b84d4d
36 changed files with 3752 additions and 3 deletions
+403
View File
@@ -0,0 +1,403 @@
/***********************************************************************************************************************************************************************
* File Name : AdminTaskContext.jsx
* Type : Context / Provider
* Description : Admin task management context.
* Covers: task lists (list, get, create, update, archive, restore, bulk archive, bulk restore)
* tasks (list, get, create, update, archive, restore, bulk archive, bulk restore)
* task list groups (list assigned, assign, unassign)
***********************************************************************************************************************************************************************/
import { createContext, useCallback, useContext, useState } from 'react';
import api from '@/utils/api.util';
import { toast } from 'sonner';
const BASE = '/admin/task-lists';
// ─── Context ──────────────────────────────────────────────────────────────────
const AdminTaskContext = createContext(null);
export function useAdminTask() {
const ctx = useContext(AdminTaskContext);
if (!ctx) throw new Error('useAdminTask must be used within an AdminTaskProvider');
return ctx;
}
// ─── Provider ─────────────────────────────────────────────────────────────────
export function AdminTaskProvider({ children }) {
// ── Task List state ───────────────────────────────────────────────────────
const [taskLists, setTaskLists] = useState([]);
const [taskList, setTaskList] = useState(null);
// ── Task state ────────────────────────────────────────────────────────────
const [tasks, setTasks] = useState([]);
const [task, setTask] = useState(null);
// ── Task List Groups state ────────────────────────────────────────────────
const [taskListGroups, setTaskListGroups] = useState([]);
// ── Shared ────────────────────────────────────────────────────────────────
const [attributes, setAttributes] = useState([]);
const [pagination, setPagination] = useState({ page: 1, limit: 10, totalRecords: 0, totalPages: 1 });
const [loading, setLoading] = useState(false);
// ─── Generic request wrapper ──────────────────────────────────────────────
const request = useCallback(async (fn) => {
setLoading(true);
try {
return await fn();
} catch (err) {
const message = err?.response?.data?.message ?? 'Something went wrong.';
toast.error(message);
return null;
} finally {
setLoading(false);
}
}, []);
// ══════════════════════════════════════════════════════════════════════════
// TASK LISTS
// ══════════════════════════════════════════════════════════════════════════
// ─── GET ALL ──────────────────────────────────────────────────────────────
const fetchTaskLists = useCallback(
({ page = 1, limit = 10, filters = [], sort = [], archived = false } = {}) =>
request(async () => {
const res = await api.get(BASE, {
params: {
page,
limit,
filters: JSON.stringify(filters),
sort: JSON.stringify(sort),
archived: archived ? 'true' : undefined,
},
});
const { data, attributes: attrs, pagination: pg } = res.data?.data ?? {};
setTaskLists(data ?? []);
setAttributes(attrs ?? []);
setPagination(pg ?? { page: 1, limit: 10, totalRecords: 0, totalPages: 1 });
}),
[request]
);
// ─── GET ONE ──────────────────────────────────────────────────────────────
// Response now includes a `groups` array on the task list object.
const fetchTaskList = useCallback(
(taskListId) =>
request(async () => {
const res = await api.get(`${BASE}/${taskListId}`);
const taskListData = res.data?.data ?? null;
setTaskList(taskListData);
// Sync the groups slice from the embedded payload so consumers
// don't have to call fetchTaskListGroups separately after a getOne.
if (taskListData?.groups) setTaskListGroups(taskListData.groups);
return taskListData;
}),
[request]
);
// ─── GET ARCHIVED TASK LISTS ──────────────────────────────────────────────
const fetchArchivedTaskLists = useCallback(
({ page = 1, limit = 10, filters = [], sort = [] } = {}) =>
request(async () => {
const res = await api.get(`${BASE}/archived`, {
params: {
page,
limit,
filters: JSON.stringify(filters),
sort: JSON.stringify(sort),
},
});
const { data, attributes: attrs, pagination: pg } = res.data?.data ?? {};
setTaskLists(data ?? []);
setAttributes(attrs ?? []);
setPagination(pg ?? { page: 1, limit: 10, totalRecords: 0, totalPages: 1 });
}),
[request]
);
// ─── GET ARCHIVED TASKS ───────────────────────────────────────────────────
const fetchArchivedTasks = useCallback(
(taskListId, { page = 1, limit = 10, filters = [], sort = [] } = {}) =>
request(async () => {
const res = await api.get(`${BASE}/${taskListId}/tasks/archived`, {
params: {
page,
limit,
filters: JSON.stringify(filters),
sort: JSON.stringify(sort),
},
});
const { data, attributes: attrs, pagination: pg } = res.data?.data ?? {};
setTasks(data ?? []);
setAttributes(attrs ?? []);
setPagination(pg ?? { page: 1, limit: 10, totalRecords: 0, totalPages: 1 });
}),
[request]
);
// ─── CREATE ───────────────────────────────────────────────────────────────
const createTaskList = useCallback(
(payload) =>
request(async () => {
const res = await api.post(BASE, payload);
toast.success('Task list created.');
return res.data?.data ?? null;
}),
[request]
);
// ─── UPDATE ───────────────────────────────────────────────────────────────
const updateTaskList = useCallback(
(taskListId, payload) =>
request(async () => {
const res = await api.patch(`${BASE}/${taskListId}`, payload);
toast.success('Task list updated.');
return res.data?.data?.data ?? null;
}),
[request]
);
// ─── ARCHIVE ──────────────────────────────────────────────────────────────
const archiveTaskList = useCallback(
(taskListId) =>
request(async () => {
await api.delete(`${BASE}/${taskListId}`);
toast.success('Task list archived.');
return true;
}),
[request]
);
// ─── RESTORE ──────────────────────────────────────────────────────────────
const restoreTaskList = useCallback(
(taskListId) =>
request(async () => {
await api.patch(`${BASE}/${taskListId}/restore`);
toast.success('Task list restored.');
return true;
}),
[request]
);
// ─── BULK ARCHIVE ─────────────────────────────────────────────────────────
const bulkArchiveTaskLists = useCallback(
(ids) =>
request(async () => {
await api.post(`${BASE}/bulk-archive`, { ids });
toast.success(`${ids.length} task list(s) archived.`);
return true;
}),
[request]
);
// ─── BULK RESTORE ─────────────────────────────────────────────────────────
const bulkRestoreTaskLists = useCallback(
(ids) =>
request(async () => {
await api.post(`${BASE}/bulk-restore`, { ids });
toast.success(`${ids.length} task list(s) restored.`);
return true;
}),
[request]
);
// ══════════════════════════════════════════════════════════════════════════
// TASK LIST GROUPS
// ══════════════════════════════════════════════════════════════════════════
// ─── GET ASSIGNED GROUPS ──────────────────────────────────────────────────
// GET /admin/task-lists/:taskListId/groups
const fetchTaskListGroups = useCallback(
(taskListId) =>
request(async () => {
const res = await api.get(`${BASE}/${taskListId}/groups`);
const groups = res.data?.data ?? [];
setTaskListGroups(groups);
return groups;
}),
[request]
);
// ─── ASSIGN GROUPS ────────────────────────────────────────────────────────
// POST /admin/task-lists/:taskListId/groups/assign
// payload: { group_ids: number[] }
//
// Returns summary: { assigned_ids, already_assigned_ids, invalid_ids }
const assignGroups = useCallback(
(taskListId, groupIds) =>
request(async () => {
const res = await api.post(`${BASE}/${taskListId}/groups/assign`, {
group_ids: groupIds,
});
const result = res.data?.data ?? {};
if (result.assigned_ids?.length) {
toast.success(`${result.assigned_ids.length} group(s) assigned.`);
} else {
toast.info('All selected groups were already assigned.');
}
return result;
}),
[request]
);
// ─── UNASSIGN GROUPS ──────────────────────────────────────────────────────
// POST /admin/task-lists/:taskListId/groups/unassign
// payload: { group_ids: number[] }
//
// Returns summary: { unassigned_ids, skipped_ids }
const unassignGroups = useCallback(
(taskListId, groupIds) =>
request(async () => {
const res = await api.post(`${BASE}/${taskListId}/groups/unassign`, {
group_ids: groupIds,
});
const result = res.data?.data ?? {};
toast.success(`${result.unassigned_ids?.length ?? 0} group(s) unassigned.`);
return result;
}),
[request]
);
// ══════════════════════════════════════════════════════════════════════════
// TASKS
// ══════════════════════════════════════════════════════════════════════════
// ─── GET ALL ──────────────────────────────────────────────────────────────
const fetchTasks = useCallback(
(taskListId, { page = 1, limit = 10, filters = [], sort = [], archived = false } = {}) =>
request(async () => {
const res = await api.get(`${BASE}/${taskListId}/tasks`, {
params: {
page,
limit,
filters: JSON.stringify(filters),
sort: JSON.stringify(sort),
archived: archived ? 'true' : undefined,
},
});
const { data, attributes: attrs, pagination: pg } = res.data?.data ?? {};
setTasks(data ?? []);
setAttributes(attrs ?? []);
setPagination(pg ?? { page: 1, limit: 10, totalRecords: 0, totalPages: 1 });
}),
[request]
);
// ─── GET ONE ──────────────────────────────────────────────────────────────
const fetchTask = useCallback(
(taskListId, taskId) =>
request(async () => {
const res = await api.get(`${BASE}/${taskListId}/tasks/${taskId}`);
const taskData = res.data?.data ?? null;
setTask(taskData);
return taskData;
}),
[request]
);
// ─── CREATE ───────────────────────────────────────────────────────────────
const createTask = useCallback(
(taskListId, payload) =>
request(async () => {
const res = await api.post(`${BASE}/${taskListId}/tasks`, payload);
toast.success('Task created.');
return res.data?.data?.data ?? null;
}),
[request]
);
// ─── UPDATE ───────────────────────────────────────────────────────────────
const updateTask = useCallback(
(taskListId, taskId, payload) =>
request(async () => {
const res = await api.patch(`${BASE}/${taskListId}/tasks/${taskId}`, payload);
toast.success('Task updated.');
return res.data?.data?.data ?? null;
}),
[request]
);
// ─── ARCHIVE ──────────────────────────────────────────────────────────────
const archiveTask = useCallback(
(taskListId, taskId) =>
request(async () => {
await api.delete(`${BASE}/${taskListId}/tasks/${taskId}`);
toast.success('Task archived.');
return true;
}),
[request]
);
// ─── RESTORE ──────────────────────────────────────────────────────────────
const restoreTask = useCallback(
(taskListId, taskId) =>
request(async () => {
await api.patch(`${BASE}/${taskListId}/tasks/${taskId}/restore`);
toast.success('Task restored.');
return true;
}),
[request]
);
// ─── BULK ARCHIVE ─────────────────────────────────────────────────────────
const bulkArchiveTasks = useCallback(
(taskListId, ids) =>
request(async () => {
await api.post(`${BASE}/${taskListId}/tasks/bulk-archive`, { ids });
toast.success(`${ids.length} task(s) archived.`);
return true;
}),
[request]
);
// ─── BULK RESTORE ─────────────────────────────────────────────────────────
const bulkRestoreTasks = useCallback(
(taskListId, ids) =>
request(async () => {
await api.post(`${BASE}/${taskListId}/tasks/bulk-restore`, { ids });
toast.success(`${ids.length} task(s) restored.`);
return true;
}),
[request]
);
// ─────────────────────────────────────────────────────────────────────────
return (
<AdminTaskContext.Provider value={{
// state
taskLists, taskList,
tasks, task,
taskListGroups, setTaskListGroups,
attributes, setAttributes,
pagination, setPagination,
loading,
// task list actions
fetchTaskLists, fetchTaskList, fetchArchivedTaskLists,
createTaskList, updateTaskList,
archiveTaskList, restoreTaskList,
bulkArchiveTaskLists, bulkRestoreTaskLists,
// task list group actions
fetchTaskListGroups,
assignGroups,
unassignGroups,
// task actions
fetchTasks, fetchTask, fetchArchivedTasks,
createTask, updateTask,
archiveTask, restoreTask,
bulkArchiveTasks, bulkRestoreTasks,
}}>
{children}
</AdminTaskContext.Provider>
);
}
+4 -1
View File
@@ -4,6 +4,7 @@ import { AdminDashboardProvider } from "../AdminDashboardContext"
import { UserProvider } from "../AdminUserContext";
import { UserGroupProvider } from "../AdminUserGroupContext";
import { CoursesProvider } from "../AdminCoursesContext";
import { AdminTaskProvider } from "../AdminTaskContext";
export const AdminProvider = ({ children }) => {
return (
@@ -12,7 +13,9 @@ export const AdminProvider = ({ children }) => {
<UserProvider>
<UserGroupProvider>
<CoursesProvider>
{children}
<AdminTaskProvider>
{children}
</AdminTaskProvider>
</CoursesProvider>
</UserGroupProvider>
</UserProvider>