2026-05-26 02:39:46 +08:00

218 lines
6.5 KiB
JavaScript

import { useMemo, useState } from "react";
import { parseForm } from "@/shared/forms/validation";
import { usePaginatedQuery } from "@/shared/hooks/usePaginatedQuery.js";
import { useToast } from "@/shared/ui/ToastProvider.jsx";
import {
createTaskDefinition,
listTaskDefinitions,
setTaskDefinitionStatus,
updateTaskDefinition,
} from "@/features/daily-tasks/api";
import { useDailyTaskAbilities } from "@/features/daily-tasks/permissions.js";
import { dailyTaskFormSchema } from "@/features/daily-tasks/schema";
const pageSize = 50;
const emptyData = { items: [], page: 1, pageSize, total: 0 };
const emptyForm = (task = {}) => ({
category: task.category || "",
description: task.description || "",
effectiveFrom: msToDatetimeLocal(task.effectiveFromMs),
effectiveTo: msToDatetimeLocal(task.effectiveToMs),
metricType: task.metricType || "",
rewardCoinAmount: task.rewardCoinAmount === 0 || task.rewardCoinAmount ? String(task.rewardCoinAmount) : "",
sortOrder: task.sortOrder === 0 || task.sortOrder ? String(task.sortOrder) : "0",
status: task.status || "draft",
targetUnit: task.targetUnit || "count",
targetValue: task.targetValue === 0 || task.targetValue ? String(task.targetValue) : "",
taskType: task.taskType || "daily",
title: task.title || "",
});
export function useDailyTasksPage() {
const abilities = useDailyTaskAbilities();
const { showToast } = useToast();
const [query, setQuery] = useState("");
const [status, setStatus] = useState("");
const [taskType, setTaskType] = useState("");
const [category, setCategory] = useState("");
const [page, setPage] = useState(1);
const [activeAction, setActiveAction] = useState("");
const [selectedTask, setSelectedTask] = useState(null);
const [form, setForm] = useState(emptyForm);
const [loadingAction, setLoadingAction] = useState("");
const filters = useMemo(
() => ({
category,
keyword: query,
status,
task_type: taskType,
}),
[category, query, status, taskType],
);
const {
data = emptyData,
error,
loading,
reload,
} = usePaginatedQuery({
errorMessage: "加载每日任务失败",
fetcher: listTaskDefinitions,
filters,
page,
pageSize,
queryKey: ["daily-tasks", filters, page],
});
const changeQuery = resetSetter(setQuery, setPage);
const changeStatus = resetSetter(setStatus, setPage);
const changeTaskType = resetSetter(setTaskType, setPage);
const changeCategory = resetSetter(setCategory, setPage);
const resetFilters = () => {
setQuery("");
setStatus("");
setTaskType("");
setCategory("");
setPage(1);
};
const openCreate = () => {
setSelectedTask(null);
setForm(emptyForm());
setActiveAction("create");
};
const openEdit = (task) => {
if (!task?.taskId) {
return;
}
setSelectedTask(task);
setForm(emptyForm(task));
setActiveAction("edit");
};
const closeAction = () => {
setActiveAction("");
setSelectedTask(null);
};
const submitTask = async (event) => {
event.preventDefault();
const editing = activeAction === "edit";
if (editing && !selectedTask?.taskId) {
return;
}
await runAction(editing ? "edit" : "create", editing ? "任务已更新" : "任务已创建", async () => {
const payload = buildTaskPayload(parseForm(dailyTaskFormSchema, form));
if (editing) {
await updateTaskDefinition(selectedTask.taskId, payload);
} else {
await createTaskDefinition(payload);
}
closeAction();
setForm(emptyForm());
await reload();
});
};
const toggleTask = async (task, nextEnabled = task.status !== "active") => {
if (!abilities.canStatus || !task?.taskId || task.status === "archived") {
return;
}
const nextStatus = nextEnabled ? "active" : "paused";
if (task.status === nextStatus) {
return;
}
await runAction(`status-${task.taskId}`, nextEnabled ? "任务已启用" : "任务已暂停", async () => {
await setTaskDefinitionStatus(task.taskId, nextStatus);
await reload();
});
};
const runAction = async (action, successMessage, submitter) => {
setLoadingAction(action);
try {
await submitter();
showToast(successMessage, "success");
} catch (err) {
showToast(err.message || "操作失败", "error");
} finally {
setLoadingAction("");
}
};
return {
abilities,
activeAction,
category,
changeCategory,
changeQuery,
changeStatus,
changeTaskType,
closeAction,
data,
error,
form,
loading,
loadingAction,
openCreate,
openEdit,
page,
query,
reload,
resetFilters,
selectedTask,
setForm,
setPage,
status,
submitTask,
taskType,
toggleTask,
};
}
function buildTaskPayload(form) {
return {
category: form.category.trim(),
description: form.description.trim(),
effective_from_ms: datetimeLocalToMs(form.effectiveFrom),
effective_to_ms: datetimeLocalToMs(form.effectiveTo),
metric_type: form.metricType.trim(),
reward_coin_amount: Number(form.rewardCoinAmount),
sort_order: Number(form.sortOrder || 0),
status: form.status,
target_unit: form.targetUnit,
target_value: Number(form.targetValue),
task_type: form.taskType,
title: form.title.trim(),
};
}
function resetSetter(setter, setPage) {
return (value) => {
setter(value);
setPage(1);
};
}
function msToDatetimeLocal(value) {
if (!value) {
return "";
}
const date = new Date(Number(value));
if (Number.isNaN(date.getTime())) {
return "";
}
const local = new Date(date.getTime() - date.getTimezoneOffset() * 60000);
return local.toISOString().slice(0, 16);
}
function datetimeLocalToMs(value) {
const trimmed = String(value || "").trim();
if (!trimmed) {
return 0;
}
const parsed = new Date(trimmed).getTime();
return Number.isFinite(parsed) ? parsed : 0;
}