import { ChevronDown, PowerOff, RefreshCcw, Save } from 'lucide-react'; import { FormEvent, useEffect, useMemo, useState } from 'react'; import { disableProfileTaskConfig, listAdminTrackingEventKeys, listProfileTaskConfigs, upsertProfileTaskConfig, } from '../api/adminApiClient'; import type { AdminTrackingEventKeyPayload, ProfileTaskConfigAdminResponse, ProfileTaskCycle, TrackingScopeKind, } from '../api/adminApiTypes'; import { useAdminWriteConfirm } from '../components/useAdminWriteConfirm'; import { buildAdminTrackingEventKeyOptions, filterAdminTrackingEventKeyOptions, findAdminTrackingEventDefinition, } from '../config/trackingEventDefinitions'; import { handlePageError } from './pageUtils'; interface AdminTaskConfigPageProps { token: string; result: ProfileTaskConfigAdminResponse | null; onUnauthorized: (message?: string) => void; onResultChange: (result: ProfileTaskConfigAdminResponse) => void; } const taskCycles: Array<{ value: ProfileTaskCycle; label: string }> = [ { value: 'daily', label: '每日' }, ]; const profileTaskScopeKind = 'user' satisfies TrackingScopeKind; export function AdminTaskConfigPage({ token, result, onUnauthorized, onResultChange, }: AdminTaskConfigPageProps) { const [entries, setEntries] = useState([]); const [taskId, setTaskId] = useState('daily_login'); const [title, setTitle] = useState('每日登录'); const [description, setDescription] = useState(''); const [eventKey, setEventKey] = useState('daily_login'); const [eventKeySearch, setEventKeySearch] = useState('每日登录'); const [eventKeys, setEventKeys] = useState( [], ); const [isEventKeyPickerOpen, setIsEventKeyPickerOpen] = useState(false); const [cycle, setCycle] = useState('daily'); const [threshold, setThreshold] = useState('1'); const [rewardPoints, setRewardPoints] = useState('10'); const [sortOrder, setSortOrder] = useState('10'); const [enabled, setEnabled] = useState(true); const [disableTaskId, setDisableTaskId] = useState('daily_login'); const [errorMessage, setErrorMessage] = useState(''); const [disableErrorMessage, setDisableErrorMessage] = useState(''); const [listErrorMessage, setListErrorMessage] = useState(''); const [isSaving, setIsSaving] = useState(false); const [isDisabling, setIsDisabling] = useState(false); const [isLoading, setIsLoading] = useState(false); const { confirmWrite, confirmDialog } = useAdminWriteConfirm(); useEffect(() => { void refreshTaskConfigs(); void refreshTrackingEventKeys(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [token]); const eventKeyOptions = useMemo( () => buildAdminTrackingEventKeyOptions(eventKeys).filter((option) => option.scopeKinds.includes(profileTaskScopeKind), ), [eventKeys], ); const selectedEventOption = useMemo( () => eventKeyOptions.find((option) => option.key === eventKey) ?? findAdminTrackingEventDefinition(eventKey), [eventKey, eventKeyOptions], ); const filteredEventDefinitions = useMemo( () => filterAdminTrackingEventKeyOptions(eventKeyOptions, eventKeySearch), [eventKeyOptions, eventKeySearch], ); useEffect(() => { if (eventKeySearch !== eventKey) { return; } const nextOption = eventKeyOptions.find((option) => option.key === eventKey) ?? findAdminTrackingEventDefinition(eventKey); if (nextOption) { setEventKeySearch(nextOption.title); } }, [eventKey, eventKeyOptions, eventKeySearch]); async function refreshTrackingEventKeys() { try { const response = await listAdminTrackingEventKeys(token); setEventKeys(response.eventKeys); } catch { setEventKeys([]); } } async function refreshTaskConfigs() { setIsLoading(true); setListErrorMessage(''); try { const response = await listProfileTaskConfigs(token); setEntries(response.entries); const dailyLogin = response.entries.find( (entry) => entry.taskId === 'daily_login', ); if (dailyLogin) { fillForm(dailyLogin); } } catch (error: unknown) { handlePageError(error, onUnauthorized, setListErrorMessage); } finally { setIsLoading(false); } } async function handleSave(event: FormEvent) { event.preventDefault(); if (isSaving) { return; } setErrorMessage(''); const confirmed = await confirmWrite({ action: '保存任务配置', target: taskId.trim(), }); if (!confirmed) { return; } setIsSaving(true); try { const response = await upsertProfileTaskConfig(token, { taskId: taskId.trim(), title: title.trim(), description, eventKey: eventKey.trim(), cycle, scopeKind: profileTaskScopeKind, threshold: parsePositiveInteger(threshold), rewardPoints: parsePositiveInteger(rewardPoints), enabled, sortOrder: parseInteger(sortOrder), }); onResultChange(response); upsertEntry(response); } catch (error: unknown) { handlePageError(error, onUnauthorized, setErrorMessage); } finally { setIsSaving(false); } } async function handleDisable(event: FormEvent) { event.preventDefault(); if (isDisabling) { return; } setDisableErrorMessage(''); const confirmed = await confirmWrite({ action: '停用任务配置', target: disableTaskId.trim(), }); if (!confirmed) { return; } setIsDisabling(true); try { const response = await disableProfileTaskConfig(token, { taskId: disableTaskId.trim(), }); onResultChange(response); upsertEntry(response); fillForm(response); } catch (error: unknown) { handlePageError(error, onUnauthorized, setDisableErrorMessage); } finally { setIsDisabling(false); } } function upsertEntry(next: ProfileTaskConfigAdminResponse) { setEntries((current) => { const rest = current.filter((entry) => entry.taskId !== next.taskId); return [...rest, next].sort((left, right) => { if (left.sortOrder !== right.sortOrder) { return left.sortOrder - right.sortOrder; } return left.taskId.localeCompare(right.taskId); }); }); } function fillForm(entry: ProfileTaskConfigAdminResponse) { setTaskId(entry.taskId); setTitle(entry.title); setDescription(entry.description); setEventKey(entry.eventKey); setCycle(entry.cycle); setThreshold(String(entry.threshold)); setRewardPoints(String(entry.rewardPoints)); setSortOrder(String(entry.sortOrder)); setEnabled(entry.enabled); setDisableTaskId(entry.taskId); const nextOption = eventKeyOptions.find((option) => option.key === entry.eventKey) ?? findAdminTrackingEventDefinition(entry.eventKey); setEventKeySearch(nextOption?.title ?? entry.eventKey); setIsEventKeyPickerOpen(false); } function selectEventKey(nextEventKey: string) { const nextOption = eventKeyOptions.find((option) => option.key === nextEventKey) ?? findAdminTrackingEventDefinition(nextEventKey); setEventKey(nextEventKey); if (nextOption) { setEventKeySearch(nextOption.title); } else { setEventKeySearch(nextEventKey); } setIsEventKeyPickerOpen(false); } return (

任务配置

默认每日登录任务

{listErrorMessage ? (
{listErrorMessage}
) : null}