This commit is contained in:
rgrgogu
2026-06-24 13:43:28 +08:00
parent 93c3c688ca
commit b03b204861
35 changed files with 4238 additions and 2 deletions
+418
View File
@@ -0,0 +1,418 @@
/***********************************************************************************************************************************************************************
* File Name: StaffTaskContext.jsx
* Type of Program: Context
* Description: Staff task management context — aligned to the updated tasks.ctrl.js.
* Supports pagination, archive/restore, bulk ops, and filter field values
* to match the DataTable-based TaskListsPage.
***********************************************************************************************************************************************************************/
import { createContext, useContext, useState, useCallback } from "react";
import api from "@/utils/api.util";
import { toast } from "sonner";
const StaffTaskContext = createContext(null);
export const useStaffTasks = () => {
const ctx = useContext(StaffTaskContext);
if (!ctx) throw new Error("useStaffTasks must be used inside StaffTaskProvider");
return ctx;
};
const BASE = "/staff";
const PAGINATION_INIT = {
page: 1,
limit: 10,
totalRecords: 0,
totalPages: 0,
hasPrevPage: false,
hasNextPage: false,
};
export const StaffTaskProvider = ({ children }) => {
// ── Task Lists ──────────────────────────────────────────────────────────────
const [taskLists, setTaskLists] = useState([]);
const [taskList, setTaskList] = useState(null);
const [attributes, setAttributes] = useState([]);
const [pagination, setPagination] = useState(PAGINATION_INIT);
// ── Tasks ───────────────────────────────────────────────────────────────────
const [tasks, setTasks] = useState([]);
const [task, setTask] = useState(null);
const [taskAttrs, setTaskAttrs] = useState([]);
const [taskPagination, setTaskPagination] = useState(PAGINATION_INIT);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
// ── Internal request wrapper ────────────────────────────────────────────────
const request = useCallback(async (fn) => {
setLoading(true);
setError(null);
try {
return await fn();
} catch (err) {
const message = err?.response?.data?.message || err.message || "Something went wrong.";
setError(message);
toast.error(message);
return null;
} finally {
setLoading(false);
}
}, []);
// ── Build query params from DataTable fetch args ────────────────────────────
const buildParams = ({ page, limit, filters, sort, archived } = {}) => ({
...(page && { page }),
...(limit && { limit }),
...(archived !== undefined && { archived }),
...(filters?.length && { filters: JSON.stringify(filters) }),
...(sort?.length && { sort: JSON.stringify(sort) }),
});
// ════════════════════════════════════════════════════════════════════════════
// TASK LISTS
// ════════════════════════════════════════════════════════════════════════════
// ─── GET /staff/task-lists ───────────────────────────────────────────────
const fetchTaskLists = useCallback(
(args = {}) =>
request(async () => {
const res = await api.get(`${BASE}/task-lists`, { params: buildParams(args) });
const { data, attributes, pagination } = res.data?.data;
setTaskLists(data ?? []);
setAttributes(attributes ?? []);
setPagination(pagination ?? null);
return res.data;
}),
[request]
);
// ─── GET /staff/task-lists/archived ──────────────────────────────────────
const fetchArchivedTaskLists = useCallback(
(args = {}) =>
request(async () => {
const res = await api.get(`${BASE}/task-lists/archived`, { params: buildParams(args) });
const payload = res.data?.data;
setTaskLists(payload?.rows ?? payload ?? []);
setAttributes(payload?.attributes ?? []);
setPagination(payload?.pagination ?? null);
return res.data;
}),
[request]
);
// ─── GET /staff/task-lists/field-values ──────────────────────────────────
const fetchTaskListFieldValues = useCallback(
(field, args = {}) =>
request(async () => {
const res = await api.get(`${BASE}/task-lists/field-values`, {
params: { field, ...buildParams(args) },
});
return res.data;
}),
[request]
);
// ─── GET /staff/task-lists/:taskListId ───────────────────────────────────
const fetchTaskList = useCallback(
(taskListId) =>
request(async () => {
const res = await api.get(`${BASE}/task-lists/${taskListId}`);
setTaskList(res.data?.data ?? null);
return res.data;
}),
[request]
);
// ─── POST /staff/task-lists ──────────────────────────────────────────────
// body: { name, description, group_ids: ["27", "28"] }
const addTaskList = useCallback(
(payload) =>
request(async () => {
const res = await api.post(`${BASE}/task-lists`, payload);
const created = res.data?.data;
if (created) setTaskLists((prev) => [created, ...prev]);
toast.success("Task list created.");
return res.data;
}),
[request]
);
// ─── PUT /staff/task-lists/:taskListId ───────────────────────────────────
const updateTaskList = useCallback(
(taskListId, payload) =>
request(async () => {
const res = await api.put(`${BASE}/task-lists/${taskListId}`, payload);
const updated = res.data?.data;
if (updated) {
setTaskLists((prev) =>
prev.map((tl) => (tl.task_list_id === taskListId ? { ...tl, ...updated } : tl))
);
if (taskList?.task_list_id === taskListId)
setTaskList((prev) => ({ ...prev, ...updated }));
}
toast.success("Task list updated.");
return res.data;
}),
[request, taskList]
);
// ─── POST /staff/task-lists/:taskListId/archive ──────────────────────────
const archiveTaskList = useCallback(
(taskListId) =>
request(async () => {
const res = await api.post(`${BASE}/task-lists/${taskListId}/archive`);
setTaskLists((prev) => prev.filter((tl) => tl.task_list_id !== taskListId));
if (taskList?.task_list_id === taskListId) setTaskList(null);
toast.success("Task list archived.");
return res.data;
}),
[request, taskList]
);
// ─── POST /staff/task-lists/:taskListId/restore ──────────────────────────
const restoreTaskList = useCallback(
(taskListId) =>
request(async () => {
const res = await api.post(`${BASE}/task-lists/${taskListId}/restore`);
setTaskLists((prev) => prev.filter((tl) => tl.task_list_id !== taskListId));
toast.success("Task list restored.");
return res.data;
}),
[request]
);
// ─── POST /staff/task-lists/bulk-archive ─────────────────────────────────
const bulkArchiveTaskLists = useCallback(
(ids) =>
request(async () => {
const res = await api.post(`${BASE}/task-lists/bulk-archive`, { ids });
const { archived_ids = [] } = res.data?.data ?? {};
setTaskLists((prev) =>
prev.filter((tl) => !archived_ids.includes(tl.task_list_id))
);
toast.success(`${archived_ids.length} task list(s) archived.`);
return res.data;
}),
[request]
);
// ─── POST /staff/task-lists/bulk-restore ─────────────────────────────────
const bulkRestoreTaskLists = useCallback(
(ids) =>
request(async () => {
const res = await api.post(`${BASE}/task-lists/bulk-restore`, { ids });
const { restored_ids = [] } = res.data?.data ?? {};
setTaskLists((prev) =>
prev.filter((tl) => !restored_ids.includes(tl.task_list_id))
);
toast.success(`${restored_ids.length} task list(s) restored.`);
return res.data;
}),
[request]
);
// ════════════════════════════════════════════════════════════════════════════
// TASKS
// ════════════════════════════════════════════════════════════════════════════
// ─── GET /staff/task-lists/:taskListId/tasks ─────────────────────────────
const fetchTasks = useCallback(
(taskListId, args = {}) =>
request(async () => {
const res = await api.get(`${BASE}/task-lists/${taskListId}/tasks`, {
params: buildParams(args),
});
const payload = res.data?.data;
setTasks(payload?.rows ?? payload ?? []);
setTaskAttrs(payload?.attributes ?? []);
setTaskPagination(payload?.pagination ?? null);
return res.data;
}),
[request]
);
// ─── GET /staff/task-lists/:taskListId/tasks/archived ────────────────────
const fetchArchivedTasks = useCallback(
(taskListId, args = {}) =>
request(async () => {
const res = await api.get(`${BASE}/task-lists/${taskListId}/tasks/archived`, {
params: buildParams(args),
});
const payload = res.data?.data;
setTasks(payload?.rows ?? payload ?? []);
setTaskAttrs(payload?.attributes ?? []);
setTaskPagination(payload?.pagination ?? null);
return res.data;
}),
[request]
);
// ─── GET /staff/task-lists/:taskListId/tasks/field-values ────────────────
const fetchTaskFieldValues = useCallback(
(taskListId, field, args = {}) =>
request(async () => {
const res = await api.get(`${BASE}/task-lists/${taskListId}/tasks/field-values`, {
params: { field, ...buildParams(args) },
});
return res.data;
}),
[request]
);
// ─── GET /staff/task-lists/:taskListId/tasks/:taskId ─────────────────────
const fetchTask = useCallback(
(taskListId, taskId) =>
request(async () => {
const res = await api.get(`${BASE}/task-lists/${taskListId}/tasks/${taskId}`);
setTask(res.data?.data ?? null);
return res.data;
}),
[request]
);
// ─── POST /staff/task-lists/:taskListId/tasks ────────────────────────────
// body: { name, description, deadline, requirements: [{ type, ...fields }] }
const addTask = useCallback(
(taskListId, payload) =>
request(async () => {
const res = await api.post(`${BASE}/task-lists/${taskListId}/tasks`, payload);
const created = res.data?.data;
if (created) {
setTasks((prev) => [...prev, created]);
setTaskLists((prev) =>
prev.map((tl) =>
tl.task_list_id === taskListId
? { ...tl, tasks: [...(tl.tasks ?? []), created] }
: tl
)
);
}
toast.success("Task created.");
return res.data;
}),
[request]
);
// ─── PUT /staff/task-lists/:taskListId/tasks/:taskId ─────────────────────
const updateTask = useCallback(
(taskListId, taskId, payload) =>
request(async () => {
const res = await api.put(`${BASE}/task-lists/${taskListId}/tasks/${taskId}`, payload);
const updated = res.data?.data;
if (updated) {
setTasks((prev) =>
prev.map((t) => (t.task_id === taskId ? { ...t, ...updated } : t))
);
setTaskLists((prev) =>
prev.map((tl) =>
tl.task_list_id === taskListId
? {
...tl,
tasks: tl.tasks?.map((t) =>
t.task_id === taskId ? { ...t, ...updated } : t
),
}
: tl
)
);
if (task?.task_id === taskId) setTask((prev) => ({ ...prev, ...updated }));
}
toast.success("Task updated.");
return res.data;
}),
[request, task]
);
// ─── POST /staff/task-lists/:taskListId/tasks/:taskId/archive ────────────
const archiveTask = useCallback(
(taskListId, taskId) =>
request(async () => {
const res = await api.post(`${BASE}/task-lists/${taskListId}/tasks/${taskId}/archive`);
setTasks((prev) => prev.filter((t) => t.task_id !== taskId));
setTaskLists((prev) =>
prev.map((tl) =>
tl.task_list_id === taskListId
? { ...tl, tasks: tl.tasks?.filter((t) => t.task_id !== taskId) }
: tl
)
);
toast.success("Task archived.");
return res.data;
}),
[request]
);
// ─── POST /staff/task-lists/:taskListId/tasks/:taskId/restore ────────────
const restoreTask = useCallback(
(taskListId, taskId) =>
request(async () => {
const res = await api.post(`${BASE}/task-lists/${taskListId}/tasks/${taskId}/restore`);
setTasks((prev) => prev.filter((t) => t.task_id !== taskId));
toast.success("Task restored.");
return res.data;
}),
[request]
);
// ─── POST /staff/task-lists/:taskListId/tasks/bulk-archive ───────────────
const bulkArchiveTasks = useCallback(
(taskListId, ids) =>
request(async () => {
const res = await api.post(`${BASE}/task-lists/${taskListId}/tasks/bulk-archive`, { ids });
const { archived_ids = [] } = res.data?.data ?? {};
setTasks((prev) => prev.filter((t) => !archived_ids.includes(t.task_id)));
toast.success(`${archived_ids.length} task(s) archived.`);
return res.data;
}),
[request]
);
// ─── POST /staff/task-lists/:taskListId/tasks/bulk-restore ───────────────
const bulkRestoreTasks = useCallback(
(taskListId, ids) =>
request(async () => {
const res = await api.post(`${BASE}/task-lists/${taskListId}/tasks/bulk-restore`, { ids });
const { restored_ids = [] } = res.data?.data ?? {};
setTasks((prev) => prev.filter((t) => !restored_ids.includes(t.task_id)));
toast.success(`${restored_ids.length} task(s) restored.`);
return res.data;
}),
[request]
);
return (
<StaffTaskContext.Provider
value={{
// ── Task List state
taskLists, taskList, attributes, pagination,
setPagination, setTaskList,
// ── Task List actions
fetchTaskLists, fetchArchivedTaskLists, fetchTaskListFieldValues,
fetchTaskList,
addTaskList, updateTaskList,
archiveTaskList, restoreTaskList,
bulkArchiveTaskLists, bulkRestoreTaskLists,
// ── Task state
tasks, task, taskAttrs, taskPagination,
setTask,
// ── Task actions
fetchTasks, fetchArchivedTasks, fetchTaskFieldValues,
fetchTask,
addTask, updateTask,
archiveTask, restoreTask,
bulkArchiveTasks, bulkRestoreTasks,
// ── Shared
loading, error,
}}
>
{children}
</StaffTaskContext.Provider>
);
};