716 lines
22 KiB
TypeScript
716 lines
22 KiB
TypeScript
import {
|
|
createEffect,
|
|
createMemo,
|
|
createSignal,
|
|
on,
|
|
onCleanup,
|
|
onMount,
|
|
} from "solid-js";
|
|
import AccountSidebar from "~/components/account/AccountSidebar";
|
|
import AddAccountDialog, {
|
|
type LoginForm,
|
|
} from "~/components/account/AddAccountDialog";
|
|
import CourseWorkspace from "~/components/account/CourseWorkspace";
|
|
import {
|
|
createWkClient,
|
|
loginApi,
|
|
runStudyQueue,
|
|
type CourseKind,
|
|
type RecordType,
|
|
} from "~/service/wk";
|
|
import { setUnauthorizedHandler } from "~/service/http";
|
|
import { accountStore, type AccountItem } from "~/store/account";
|
|
import { getMergedHosts, settingsStore } from "~/store/settings";
|
|
import type { CourseType } from "~/types/Course";
|
|
|
|
const statusOptions: { label: string; value: CourseKind }[] = [
|
|
{ label: "我的课程", value: "run" },
|
|
{ label: "已结束", value: "finish" },
|
|
{ label: "报名中", value: "sign" },
|
|
];
|
|
|
|
const recordTypeOptions: { label: string; value: RecordType }[] = [
|
|
{ label: "课程", value: "" },
|
|
{ label: "作业", value: "/work" },
|
|
{ label: "考试", value: "/exam" },
|
|
{ label: "讨论", value: "/discuss" },
|
|
];
|
|
|
|
const createDefaultForm = (host: string): LoginForm => ({
|
|
username: "",
|
|
password: "",
|
|
token: "",
|
|
status: "run",
|
|
host,
|
|
});
|
|
|
|
const stripHtml = (value: string) => value.replace(/<[^>]+>/g, "").trim();
|
|
|
|
const parseDurationToSeconds = (value: string) => {
|
|
const parts = value.split(":").map(Number);
|
|
if (parts.length !== 3 || parts.some((part) => Number.isNaN(part))) {
|
|
return 0;
|
|
}
|
|
|
|
const [hours, minutes, seconds] = parts;
|
|
return hours * 3600 + minutes * 60 + seconds;
|
|
};
|
|
|
|
const createRecordCacheKey = (
|
|
accountId: string,
|
|
courseId: number,
|
|
recordType: RecordType,
|
|
) => `${accountId}::${courseId}::${recordType || "course"}`;
|
|
|
|
const Account = () => {
|
|
const [storeState, setStoreState] = createSignal(accountStore.getState());
|
|
const [settingsState, setSettingsState] = createSignal(
|
|
settingsStore.getState(),
|
|
);
|
|
const [showDialog, setShowDialog] = createSignal(false);
|
|
const [isSubmitting, setIsSubmitting] = createSignal(false);
|
|
const [loggingOutId, setLoggingOutId] = createSignal("");
|
|
const [isRefreshingAccount, setIsRefreshingAccount] = createSignal(false);
|
|
const [errorMessage, setErrorMessage] = createSignal("");
|
|
const [form, setForm] = createSignal<LoginForm>(
|
|
createDefaultForm("cqcst.leykeji.com"),
|
|
);
|
|
const [recordsLoading, setRecordsLoading] = createSignal(false);
|
|
const [recordError, setRecordError] = createSignal("");
|
|
const [isRefreshingRecords, setIsRefreshingRecords] = createSignal(false);
|
|
const [isRefreshingCourses, setIsRefreshingCourses] = createSignal(false);
|
|
const [isRefreshingLogs, setIsRefreshingLogs] = createSignal(false);
|
|
const [hasRestoredRecords, setHasRestoredRecords] = createSignal(false);
|
|
const accountClients = new Map<string, ReturnType<typeof createWkClient>>();
|
|
|
|
onMount(() => {
|
|
const unsubscribeAccount = accountStore.subscribe((state) => {
|
|
setStoreState(state);
|
|
});
|
|
const unsubscribeSettings = settingsStore.subscribe((state) => {
|
|
setSettingsState(state);
|
|
});
|
|
setUnauthorizedHandler(reloginBySession);
|
|
|
|
onCleanup(() => {
|
|
unsubscribeAccount();
|
|
unsubscribeSettings();
|
|
setUnauthorizedHandler(null);
|
|
});
|
|
});
|
|
|
|
const accounts = createMemo(() => storeState().accounts);
|
|
const selectedAccountId = createMemo(() => storeState().selectedAccountId);
|
|
const expandedAccountId = createMemo(() => storeState().expandedAccountId);
|
|
const selectedCourseId = createMemo(() => storeState().selectedCourseId);
|
|
const courseKind = createMemo(() => storeState().courseKind);
|
|
const recordType = createMemo(() => storeState().recordType);
|
|
const records = createMemo(() => storeState().records);
|
|
const recordCacheMap = createMemo(() => storeState().recordCacheMap);
|
|
const studyLogsMap = createMemo(() => storeState().studyLogsMap);
|
|
const runningStudyMap = createMemo(() => storeState().runningStudyMap);
|
|
const mergedHostOptions = createMemo(() =>
|
|
getMergedHosts(settingsState().localHosts, settingsState().remoteHosts),
|
|
);
|
|
const hostLabels = createMemo(
|
|
() =>
|
|
Object.fromEntries(
|
|
mergedHostOptions().map((item) => [item.host, item.label]),
|
|
) as Record<string, string>,
|
|
);
|
|
const defaultHost = createMemo(
|
|
() => mergedHostOptions()[0]?.host ?? "cqcst.leykeji.com",
|
|
);
|
|
const currentCourseKindLabel = createMemo(
|
|
() =>
|
|
statusOptions.find((item) => item.value === courseKind())?.label ??
|
|
courseKind(),
|
|
);
|
|
const showingCachedRecords = createMemo(() => {
|
|
const accountId = selectedAccountId();
|
|
const courseId = selectedCourseId();
|
|
|
|
if (!accountId || !courseId || recordsLoading() || isRefreshingRecords()) {
|
|
return false;
|
|
}
|
|
|
|
return (
|
|
createRecordCacheKey(accountId, courseId, recordType()) in
|
|
recordCacheMap()
|
|
);
|
|
});
|
|
|
|
const selectedAccount = createMemo(() => {
|
|
return accounts().find((item) => item.id === selectedAccountId()) ?? null;
|
|
});
|
|
|
|
const selectedCourseList = createMemo<CourseType[]>(() => {
|
|
return selectedAccount()?.courses ?? [];
|
|
});
|
|
|
|
const selectedCourse = createMemo(() => {
|
|
return (
|
|
selectedCourseList().find((item) => item.id === selectedCourseId()) ??
|
|
null
|
|
);
|
|
});
|
|
|
|
const isRunningStudy = createMemo(() => {
|
|
const account = selectedAccount();
|
|
if (!account) {
|
|
return false;
|
|
}
|
|
|
|
return !!runningStudyMap()[account.id];
|
|
});
|
|
|
|
const studyLogs = createMemo(() => {
|
|
const account = selectedAccount();
|
|
if (!account) {
|
|
return [];
|
|
}
|
|
|
|
return studyLogsMap()[account.id] ?? [];
|
|
});
|
|
|
|
const updateForm = <K extends keyof LoginForm>(
|
|
key: K,
|
|
value: LoginForm[K],
|
|
) => {
|
|
setForm((prev) => ({ ...prev, [key]: value }));
|
|
};
|
|
|
|
const appendStudyLog = (message: string, accountId?: string) => {
|
|
const targetAccountId = accountId ?? selectedAccount()?.id;
|
|
if (!targetAccountId) {
|
|
return;
|
|
}
|
|
|
|
accountStore.getState().appendStudyLog(targetAccountId, message);
|
|
};
|
|
|
|
const getAccountClient = (accountId: string) => {
|
|
const existingClient = accountClients.get(accountId);
|
|
if (existingClient) {
|
|
return existingClient;
|
|
}
|
|
|
|
const client = createWkClient(() => {
|
|
return accountStore
|
|
.getState()
|
|
.accounts.find((item) => item.id === accountId)?.sessionId;
|
|
});
|
|
|
|
accountClients.set(accountId, client);
|
|
return client;
|
|
};
|
|
|
|
const reloginBySession = async (sessionId: string) => {
|
|
if (!sessionId) {
|
|
return false;
|
|
}
|
|
|
|
const target = accountStore
|
|
.getState()
|
|
.accounts.find((item) => item.sessionId === sessionId);
|
|
|
|
if (!target) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
const res = await loginApi({
|
|
username: target.username,
|
|
password: target.auth.password,
|
|
token: target.auth.token,
|
|
status: target.status,
|
|
host: target.host,
|
|
});
|
|
|
|
accountStore.getState().upsertAccount({
|
|
...target,
|
|
sessionId: res.data.session_id,
|
|
user: res.data.user,
|
|
courses: res.data.courses ?? target.courses,
|
|
});
|
|
await loadCourses(target.id, target.status);
|
|
appendStudyLog(`会话失效,已重新登录:${target.user.name}`, target.id);
|
|
return true;
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : "重新登录失败";
|
|
setErrorMessage(message);
|
|
appendStudyLog(`重新登录失败:${message}`, target.id);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
const openDialog = () => {
|
|
setErrorMessage("");
|
|
setForm(createDefaultForm(defaultHost()));
|
|
setShowDialog(true);
|
|
};
|
|
|
|
const closeDialog = () => {
|
|
if (isSubmitting()) {
|
|
return;
|
|
}
|
|
|
|
setShowDialog(false);
|
|
setErrorMessage("");
|
|
};
|
|
|
|
const handleAddAccount = async () => {
|
|
const payload = form();
|
|
const hasAccount =
|
|
payload.username.trim() !== "" && payload.password.trim() !== "";
|
|
const hasCookie = payload.token.trim() !== "";
|
|
|
|
if (!hasAccount && !hasCookie) {
|
|
setErrorMessage("请填写账号和密码,或者填写 Cookie。");
|
|
return;
|
|
}
|
|
|
|
setIsSubmitting(true);
|
|
setErrorMessage("");
|
|
|
|
try {
|
|
const res = await loginApi({
|
|
username: payload.username.trim(),
|
|
password: payload.password.trim(),
|
|
token: payload.token.trim(),
|
|
status: payload.status,
|
|
host: payload.host,
|
|
});
|
|
|
|
const accountId = `${res.data.user.id}-${payload.host}-${payload.status}`;
|
|
const nextAccount: AccountItem = {
|
|
id: accountId,
|
|
username: payload.username.trim() || res.data.user.id,
|
|
host: payload.host,
|
|
status: payload.status,
|
|
sessionId: res.data.session_id,
|
|
auth: {
|
|
password: payload.password.trim(),
|
|
token: payload.token.trim(),
|
|
},
|
|
user: res.data.user,
|
|
courses: res.data.courses ?? [],
|
|
};
|
|
|
|
accountStore.getState().upsertAccount(nextAccount);
|
|
await loadCourses(nextAccount.id, nextAccount.status);
|
|
accountStore.getState().setSelectedCourseId(null);
|
|
accountStore.getState().setRecords([]);
|
|
setShowDialog(false);
|
|
setForm(createDefaultForm(defaultHost()));
|
|
} catch (error) {
|
|
const message =
|
|
error instanceof Error ? error.message : "登录失败,请检查输入信息。";
|
|
setErrorMessage(message);
|
|
} finally {
|
|
setIsSubmitting(false);
|
|
}
|
|
};
|
|
|
|
const handleSelectAccount = (accountId: string) => {
|
|
accountStore.getState().setSelectedAccountId(accountId);
|
|
accountStore.getState().setSelectedCourseId(null);
|
|
accountStore.getState().setRecords([]);
|
|
setRecordError("");
|
|
};
|
|
|
|
const handleToggleExpand = (accountId: string) => {
|
|
const nextId = expandedAccountId() === accountId ? "" : accountId;
|
|
accountStore.getState().setExpandedAccountId(nextId);
|
|
};
|
|
|
|
const loadCourses = async (
|
|
accountId = selectedAccount()?.id,
|
|
status = courseKind(),
|
|
) => {
|
|
if (!accountId) {
|
|
return;
|
|
}
|
|
|
|
setIsRefreshingCourses(true);
|
|
setErrorMessage("");
|
|
|
|
try {
|
|
const res = await getAccountClient(accountId).courseApi({ status });
|
|
const courses = res.data.courses ?? [];
|
|
const account = accountStore
|
|
.getState()
|
|
.accounts.find((item) => item.id === accountId);
|
|
|
|
accountStore.getState().setAccountCourses(accountId, courses);
|
|
|
|
const currentCourseId = accountStore.getState().selectedCourseId;
|
|
if (account?.id === accountStore.getState().selectedAccountId) {
|
|
const hasSelectedCourse = courses.some(
|
|
(item) => item.id === currentCourseId,
|
|
);
|
|
if (!hasSelectedCourse) {
|
|
accountStore.getState().setSelectedCourseId(courses[0]?.id ?? null);
|
|
accountStore.getState().setRecords([]);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
const message =
|
|
error instanceof Error ? error.message : "获取课程失败,请稍后重试。";
|
|
setErrorMessage(message);
|
|
accountStore.getState().setAccountCourses(accountId, []);
|
|
if (accountStore.getState().selectedAccountId === accountId) {
|
|
accountStore.getState().setSelectedCourseId(null);
|
|
accountStore.getState().setRecords([]);
|
|
}
|
|
} finally {
|
|
setIsRefreshingCourses(false);
|
|
}
|
|
};
|
|
|
|
const handleRefreshAccount = async () => {
|
|
const account = selectedAccount();
|
|
if (!account) {
|
|
setErrorMessage("请先选择账号。");
|
|
return;
|
|
}
|
|
|
|
setIsRefreshingAccount(true);
|
|
setErrorMessage("");
|
|
|
|
try {
|
|
const res = await loginApi({
|
|
username: account.username,
|
|
password: account.auth.password,
|
|
token: account.auth.token,
|
|
status: account.status,
|
|
host: account.host,
|
|
});
|
|
|
|
accountStore.getState().upsertAccount({
|
|
...account,
|
|
sessionId: res.data.session_id,
|
|
user: res.data.user,
|
|
courses: res.data.courses ?? account.courses,
|
|
});
|
|
await loadCourses(account.id, account.status);
|
|
} catch (error) {
|
|
const message =
|
|
error instanceof Error ? error.message : "刷新账号失败,请稍后重试。";
|
|
setErrorMessage(message);
|
|
} finally {
|
|
setIsRefreshingAccount(false);
|
|
}
|
|
};
|
|
|
|
const handleLogout = async (accountId: string) => {
|
|
setLoggingOutId(accountId);
|
|
|
|
try {
|
|
const target = accounts().find((item) => item.id === accountId);
|
|
if (!target) {
|
|
return;
|
|
}
|
|
|
|
await getAccountClient(accountId).logoutApi();
|
|
accountClients.delete(accountId);
|
|
|
|
accountStore.getState().removeAccount(accountId);
|
|
if (selectedAccountId() === accountId) {
|
|
accountStore.getState().setSelectedCourseId(null);
|
|
accountStore.getState().setRecords([]);
|
|
}
|
|
} catch (error) {
|
|
const message =
|
|
error instanceof Error ? error.message : "退出登录失败,请稍后重试。";
|
|
setErrorMessage(message);
|
|
} finally {
|
|
setLoggingOutId("");
|
|
}
|
|
};
|
|
|
|
const loadCourseRecords = async (
|
|
courseId: number,
|
|
nextRecordType = recordType(),
|
|
) => {
|
|
const account = selectedAccount();
|
|
if (!account) {
|
|
return;
|
|
}
|
|
|
|
setRecordsLoading(true);
|
|
setIsRefreshingRecords(true);
|
|
setRecordError("");
|
|
|
|
try {
|
|
const res = await getAccountClient(account.id).recordApi({
|
|
course_id: String(courseId),
|
|
page: 0,
|
|
record_type: nextRecordType,
|
|
});
|
|
accountStore.getState().setRecords(res.data.list);
|
|
accountStore
|
|
.getState()
|
|
.setRecordCache(
|
|
createRecordCacheKey(account.id, courseId, nextRecordType),
|
|
res.data.list,
|
|
);
|
|
} catch (error) {
|
|
const message =
|
|
error instanceof Error ? error.message : "获取记录失败,请稍后重试。";
|
|
setRecordError(message);
|
|
accountStore.getState().setRecords([]);
|
|
} finally {
|
|
setRecordsLoading(false);
|
|
setIsRefreshingRecords(false);
|
|
}
|
|
};
|
|
|
|
const handleSelectCourse = (courseId: number) => {
|
|
accountStore.getState().setSelectedCourseId(courseId);
|
|
};
|
|
|
|
const handleRefreshRecords = async () => {
|
|
const courseId = selectedCourseId();
|
|
if (!courseId) {
|
|
setRecordError("请先选择课程。");
|
|
return;
|
|
}
|
|
|
|
await loadCourseRecords(courseId);
|
|
};
|
|
|
|
const handleClearLogs = () => {
|
|
const account = selectedAccount();
|
|
if (!account) {
|
|
return;
|
|
}
|
|
|
|
accountStore.getState().clearStudyLogs(account.id);
|
|
};
|
|
|
|
const handleRefreshLogs = () => {
|
|
setIsRefreshingLogs(true);
|
|
appendStudyLog("手动刷新日志面板");
|
|
queueMicrotask(() => {
|
|
setIsRefreshingLogs(false);
|
|
});
|
|
};
|
|
|
|
const handleStartStudy = async () => {
|
|
const account = selectedAccount();
|
|
const course = selectedCourse();
|
|
|
|
if (!account || !course) {
|
|
setRecordError("请先选择账号和课程。");
|
|
return;
|
|
}
|
|
|
|
const queueItems = records()
|
|
.filter((item) => item.progress !== "1.00")
|
|
.map((item) => ({
|
|
nodeId: item.id,
|
|
name: item.name,
|
|
currentTime: Number(item.duration || 0),
|
|
totalTime: parseDurationToSeconds(item.videoDuration),
|
|
progress: item.progress,
|
|
completed: stripHtml(item.state) === "已学",
|
|
}));
|
|
|
|
try {
|
|
accountStore.getState().setAccountRunningStudy(account.id, true);
|
|
appendStudyLog(`开始刷课:${course.name}`, account.id);
|
|
await runStudyQueue({
|
|
accountId: account.id,
|
|
courseId: course.id,
|
|
intervalSeconds: 5,
|
|
items: queueItems,
|
|
client: getAccountClient(account.id),
|
|
isRunningStudy: () =>
|
|
!!accountStore.getState().runningStudyMap[account.id],
|
|
setIsRunningStudy: () =>
|
|
accountStore.getState().setAccountRunningStudy(account.id, false),
|
|
onLog: (message: string, accoundID: string) =>
|
|
appendStudyLog(message, accoundID),
|
|
});
|
|
if (accountStore.getState().runningStudyMap[account.id]) {
|
|
appendStudyLog(`刷课完成:${course.name}`, account.id);
|
|
}
|
|
} catch (error) {
|
|
const message =
|
|
error instanceof Error ? error.message : "刷课流程执行失败。";
|
|
appendStudyLog(`刷课失败:${message}`, account.id);
|
|
setRecordError(message);
|
|
} finally {
|
|
accountStore.getState().setAccountRunningStudy(account.id, false);
|
|
}
|
|
};
|
|
|
|
const handleStopStudy = () => {
|
|
const account = selectedAccount();
|
|
if (!account) {
|
|
return;
|
|
}
|
|
|
|
accountStore.getState().setAccountRunningStudy(account.id, false);
|
|
appendStudyLog(`已发送停止刷课指令:${account.user.name}`, account.id);
|
|
};
|
|
|
|
createEffect(
|
|
on([selectedAccountId, courseKind], ([accountId, kind]) => {
|
|
if (!accountId) {
|
|
return;
|
|
}
|
|
|
|
void loadCourses(accountId, kind);
|
|
}),
|
|
);
|
|
|
|
createEffect(
|
|
on(
|
|
[selectedAccountId, selectedCourseId, recordType],
|
|
([accountId, courseId, type]) => {
|
|
if (!accountId || !courseId) {
|
|
accountStore.getState().setRecords([]);
|
|
return;
|
|
}
|
|
|
|
const cachedRecords =
|
|
recordCacheMap()[createRecordCacheKey(accountId, courseId, type)] ??
|
|
[];
|
|
accountStore.getState().setRecords(cachedRecords);
|
|
},
|
|
),
|
|
);
|
|
|
|
createEffect(() => {
|
|
const courseId = selectedCourseId();
|
|
const account = selectedAccount();
|
|
const type = recordType();
|
|
const cacheKey = account
|
|
? createRecordCacheKey(account.id, courseId ?? 0, type)
|
|
: "";
|
|
const hasCachedRecords = cacheKey ? cacheKey in recordCacheMap() : false;
|
|
|
|
if (!hasRestoredRecords()) {
|
|
setHasRestoredRecords(true);
|
|
|
|
if (courseId && account && (records().length > 0 || hasCachedRecords)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!courseId || !account) {
|
|
return;
|
|
}
|
|
|
|
if (hasCachedRecords) {
|
|
return;
|
|
}
|
|
|
|
void loadCourseRecords(courseId, type);
|
|
});
|
|
|
|
return (
|
|
<div class="flex h-full min-h-0 flex-col overflow-hidden">
|
|
<div class="flex shrink-0 items-center justify-between gap-3 rounded-[24px] border border-white/80 bg-[linear-gradient(135deg,rgba(236,254,255,0.95),rgba(255,255,255,0.92))] px-4 py-3 shadow-[0_18px_45px_-28px_rgba(8,145,178,0.35)]">
|
|
<div>
|
|
<p class="text-xs font-medium tracking-[0.26em] text-cyan-700/75 uppercase">
|
|
Account Center
|
|
</p>
|
|
<h1 class="mt-1 text-xl font-semibold text-zinc-900 sm:text-2xl">
|
|
账号管理
|
|
</h1>
|
|
<p class="mt-0.5 text-xs text-zinc-500 sm:text-sm">
|
|
管理账号登录、课程记录与运行日志
|
|
</p>
|
|
</div>
|
|
|
|
<button
|
|
class="rounded-xl bg-[linear-gradient(135deg,#06b6d4,#14b8a6)] px-3.5 py-2.5 text-sm font-medium text-white shadow-lg shadow-cyan-500/20 transition hover:-translate-y-px hover:shadow-cyan-500/30"
|
|
onClick={openDialog}
|
|
>
|
|
添加账号
|
|
</button>
|
|
</div>
|
|
|
|
<div class="mt-4 flex min-h-0 flex-1 flex-col gap-4 xl:flex-row">
|
|
<AccountSidebar
|
|
accounts={accounts()}
|
|
selectedAccountId={selectedAccountId()}
|
|
expandedAccountId={expandedAccountId()}
|
|
statusOptions={statusOptions}
|
|
currentCourseKind={courseKind()}
|
|
hostLabels={hostLabels()}
|
|
isRefreshingAccount={isRefreshingAccount()}
|
|
loggingOutId={loggingOutId()}
|
|
densityMode={settingsState().densityMode}
|
|
sidebarWidth={settingsState().sidebarWidth}
|
|
onRefreshAccount={() => void handleRefreshAccount()}
|
|
onSelectAccount={handleSelectAccount}
|
|
onToggleExpand={handleToggleExpand}
|
|
onLogout={(accountId) => void handleLogout(accountId)}
|
|
/>
|
|
|
|
<CourseWorkspace
|
|
selectedAccount={selectedAccount()}
|
|
selectedCourseList={selectedCourseList()}
|
|
selectedCourseId={selectedCourseId()}
|
|
selectedCourse={selectedCourse()}
|
|
recordType={recordType()}
|
|
courseKind={courseKind()}
|
|
currentCourseKindLabel={currentCourseKindLabel()}
|
|
showingCachedRecords={showingCachedRecords()}
|
|
recordTypeOptions={recordTypeOptions}
|
|
courseRecordTypeOptions={statusOptions}
|
|
records={records()}
|
|
studyLogs={studyLogs()}
|
|
recordsLoading={recordsLoading()}
|
|
recordError={recordError()}
|
|
isRefreshingRecords={isRefreshingRecords()}
|
|
isRefreshingCourseRecords={isRefreshingCourses()}
|
|
isRunningStudy={isRunningStudy()}
|
|
isRefreshingLogs={isRefreshingLogs()}
|
|
autoScrollLogs={settingsState().autoScrollLogs}
|
|
showLogTimestamps={settingsState().showLogTimestamps}
|
|
densityMode={settingsState().densityMode}
|
|
logFontSize={settingsState().logFontSize}
|
|
onSelectCourse={(courseId) => void handleSelectCourse(courseId)}
|
|
onRefreshRecords={() => void handleRefreshRecords()}
|
|
onRefreshCourseRecords={() => void loadCourses()}
|
|
onChangeRecordType={(value) =>
|
|
accountStore.getState().setRecordType(value)
|
|
}
|
|
onChangeCourseRecordType={(value) => {
|
|
accountStore.getState().setCourseKind(value);
|
|
accountStore.getState().setSelectedCourseId(null);
|
|
accountStore.getState().setRecords([]);
|
|
}}
|
|
onStartStudy={() => void handleStartStudy()}
|
|
onStopStudy={handleStopStudy}
|
|
onRefreshLogs={handleRefreshLogs}
|
|
onClearLogs={handleClearLogs}
|
|
renderRecordState={stripHtml}
|
|
/>
|
|
</div>
|
|
|
|
<AddAccountDialog
|
|
open={showDialog}
|
|
onClose={closeDialog}
|
|
onSubmit={() => void handleAddAccount()}
|
|
isSubmitting={isSubmitting()}
|
|
errorMessage={errorMessage()}
|
|
form={form()}
|
|
statusOptions={statusOptions}
|
|
hostOptions={mergedHostOptions().map((item) => ({
|
|
label: item.label,
|
|
host: item.host,
|
|
}))}
|
|
updateForm={updateForm}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Account;
|