/*********************************************************************************************************************************************************************** * 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 ( {children} ); };