mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
418 lines
17 KiB
React
418 lines
17 KiB
React
/***********************************************************************************************************************************************************************
|
|
* 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>
|
|
);
|
|
}; |