feat(release): bump version to 0.1.2
## 详细信息 - 升级项目版本号到 0.1.2 - 增强刷课稳定性(失败重试、心跳检测、状态自动纠正) - 优化账号页与工作台紧凑布局,增加已学/未学区分与记录筛选 - 新增首次进入更新检查、更新日志弹窗与在线下载/回退 Release 跳转
This commit is contained in:
@@ -1,4 +1,11 @@
|
||||
import { For, Show, createEffect, type JSX } from "solid-js";
|
||||
import {
|
||||
For,
|
||||
Show,
|
||||
createEffect,
|
||||
createMemo,
|
||||
createSignal,
|
||||
type JSX,
|
||||
} from "solid-js";
|
||||
import type { CourseKind, RecordType } from "~/service/wk";
|
||||
import type { AccountItem } from "~/store/account";
|
||||
import type { CourseType } from "~/types/Course";
|
||||
@@ -12,6 +19,7 @@ type CourseRecordTypeOption = {
|
||||
label: string;
|
||||
value: CourseKind;
|
||||
};
|
||||
type RecordFilter = "all" | "unlearned" | "learned";
|
||||
|
||||
interface CourseWorkspaceProps {
|
||||
selectedAccount: AccountItem | null;
|
||||
@@ -65,6 +73,7 @@ const stripTimestamp = (message: string) => {
|
||||
|
||||
const CourseWorkspace = (props: CourseWorkspaceProps) => {
|
||||
let logContainerRef: HTMLDivElement | undefined;
|
||||
const [recordFilter, setRecordFilter] = createSignal<RecordFilter>("all");
|
||||
|
||||
createEffect(() => {
|
||||
props.studyLogs.length;
|
||||
@@ -88,16 +97,44 @@ const CourseWorkspace = (props: CourseWorkspaceProps) => {
|
||||
});
|
||||
|
||||
const compact = () => props.densityMode === "compact";
|
||||
const recordStats = createMemo(() => {
|
||||
const learned = props.records.filter((record) => {
|
||||
const stateText = props.renderRecordState(record.state) || "未知状态";
|
||||
return stateText.includes("已学") || record.progress === "1.00";
|
||||
}).length;
|
||||
const total = props.records.length;
|
||||
|
||||
return {
|
||||
total,
|
||||
learned,
|
||||
unlearned: Math.max(0, total - learned),
|
||||
};
|
||||
});
|
||||
const filteredRecords = createMemo(() => {
|
||||
if (recordFilter() === "all") {
|
||||
return props.records;
|
||||
}
|
||||
|
||||
return props.records.filter((record) => {
|
||||
const stateText = props.renderRecordState(record.state) || "未知状态";
|
||||
const learned = stateText.includes("已学") || record.progress === "1.00";
|
||||
return recordFilter() === "learned" ? learned : !learned;
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<section class="flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden rounded-[28px] border border-white/80 bg-white/85 shadow-[0_18px_50px_-28px_rgba(15,23,42,0.3)]">
|
||||
<div class="flex items-center justify-between border-b border-zinc-200/80 px-5 py-4">
|
||||
<section class="flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden rounded-[22px] border border-white/80 bg-white/85 shadow-[0_12px_34px_-24px_rgba(15,23,42,0.26)]">
|
||||
<div class="flex flex-col gap-1.5 border-b border-zinc-200/80 px-3 py-2.5 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div>
|
||||
<p class="text-lg font-semibold text-zinc-900">课程工作台</p>
|
||||
<p class="mt-1 text-sm text-zinc-500">课程、记录与日志统一查看</p>
|
||||
<p class="text-[15px] font-semibold text-zinc-900 sm:text-base">
|
||||
课程工作台
|
||||
</p>
|
||||
<p class="mt-0.5 text-xs text-zinc-500 sm:text-sm">
|
||||
课程、记录与日志统一查看
|
||||
</p>
|
||||
</div>
|
||||
<Show when={props.selectedAccount}>
|
||||
<div class="rounded-full bg-cyan-50 px-3 py-1 text-sm text-cyan-700">
|
||||
<div class="rounded-full bg-cyan-50 px-2 py-0.5 text-xs text-cyan-700">
|
||||
{props.selectedAccount?.user.name}
|
||||
</div>
|
||||
</Show>
|
||||
@@ -114,19 +151,21 @@ const CourseWorkspace = (props: CourseWorkspaceProps) => {
|
||||
<div
|
||||
class={
|
||||
compact()
|
||||
? "grid min-h-0 flex-1 gap-3 p-3 xl:grid-cols-[320px_minmax(0,1fr)]"
|
||||
: "grid min-h-0 flex-1 gap-4 p-4 xl:grid-cols-[340px_minmax(0,1fr)]"
|
||||
? "grid min-h-0 flex-1 gap-1.5 p-1.5 lg:grid-cols-[280px_minmax(0,1fr)]"
|
||||
: "grid min-h-0 flex-1 gap-2 p-2 lg:grid-cols-[300px_minmax(0,1fr)]"
|
||||
}
|
||||
>
|
||||
<div class="flex min-h-0 flex-col overflow-hidden rounded-3xl border border-zinc-200 bg-[linear-gradient(180deg,rgba(248,250,252,0.9),rgba(255,255,255,0.95))]">
|
||||
<div class="flex flex-wrap items-center justify-between gap-3 border-b border-zinc-200 px-4 py-3">
|
||||
<div class="border-b border-zinc-200 px-4 py-3">
|
||||
<p class="text-sm font-semibold text-zinc-800">课程列表</p>
|
||||
<div class="flex min-h-0 flex-col overflow-hidden rounded-[18px] border border-zinc-200 bg-[linear-gradient(180deg,rgba(248,250,252,0.9),rgba(255,255,255,0.95))]">
|
||||
<div class="flex flex-wrap items-center justify-between gap-1.5 border-b border-zinc-200 px-2.5 py-2">
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-zinc-800 sm:text-sm">
|
||||
课程列表
|
||||
</p>
|
||||
<p class="mt-1 text-xs text-zinc-500">点击课程查看对应记录</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<select
|
||||
class="rounded-xl border border-zinc-200 bg-white px-3 py-2 text-sm transition outline-none focus:border-cyan-400"
|
||||
class="rounded-lg border border-zinc-200 bg-white px-2 py-1 text-xs transition outline-none focus:border-cyan-400"
|
||||
value={props.courseKind}
|
||||
onChange={(event) =>
|
||||
props.onChangeCourseRecordType(
|
||||
@@ -140,7 +179,7 @@ const CourseWorkspace = (props: CourseWorkspaceProps) => {
|
||||
</select>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-xl border border-zinc-200 bg-white px-3 py-2 text-sm text-zinc-700 transition hover:bg-zinc-100 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
class="rounded-lg border border-zinc-200 bg-white px-2 py-1 text-xs text-zinc-700 transition hover:bg-zinc-100 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
disabled={props.isRefreshingCourseRecords}
|
||||
onClick={props.onRefreshCourseRecords}
|
||||
>
|
||||
@@ -149,13 +188,7 @@ const CourseWorkspace = (props: CourseWorkspaceProps) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class={
|
||||
compact()
|
||||
? "flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto p-2.5"
|
||||
: "flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto p-3"
|
||||
}
|
||||
>
|
||||
<div class={compact() ? "flex min-h-0 flex-1 flex-col gap-1 overflow-y-auto p-1.5" : "flex min-h-0 flex-1 flex-col gap-1.5 overflow-y-auto p-2"}>
|
||||
<Show when={props.selectedCourseList.length === 0}>
|
||||
<EmptyState>
|
||||
当前“{props.currentCourseKindLabel}
|
||||
@@ -173,18 +206,18 @@ const CourseWorkspace = (props: CourseWorkspaceProps) => {
|
||||
class={
|
||||
selected()
|
||||
? compact()
|
||||
? "rounded-[20px] border border-cyan-300 bg-[linear-gradient(145deg,rgba(236,254,255,0.95),rgba(240,253,244,0.95))] px-3 py-3 text-left shadow-sm"
|
||||
: "rounded-[22px] border border-cyan-300 bg-[linear-gradient(145deg,rgba(236,254,255,0.95),rgba(240,253,244,0.95))] px-4 py-4 text-left shadow-sm"
|
||||
? "rounded-[16px] border border-cyan-300 bg-[linear-gradient(145deg,rgba(236,254,255,0.95),rgba(240,253,244,0.95))] px-2 py-2 text-left shadow-sm"
|
||||
: "rounded-[18px] border border-cyan-300 bg-[linear-gradient(145deg,rgba(236,254,255,0.95),rgba(240,253,244,0.95))] px-2.5 py-2.5 text-left shadow-sm"
|
||||
: compact()
|
||||
? "rounded-[20px] border border-zinc-200 bg-white px-3 py-3 text-left shadow-sm transition hover:border-cyan-200 hover:bg-cyan-50/30"
|
||||
: "rounded-[22px] border border-zinc-200 bg-white px-4 py-4 text-left shadow-sm transition hover:border-cyan-200 hover:bg-cyan-50/30"
|
||||
? "rounded-[16px] border border-zinc-200 bg-white px-2 py-2 text-left shadow-sm transition hover:border-cyan-200 hover:bg-cyan-50/30"
|
||||
: "rounded-[18px] border border-zinc-200 bg-white px-2.5 py-2.5 text-left shadow-sm transition hover:border-cyan-200 hover:bg-cyan-50/30"
|
||||
}
|
||||
onClick={() => props.onSelectCourse(course.id)}
|
||||
>
|
||||
<p class="truncate text-base font-semibold text-zinc-900">
|
||||
<p class="truncate text-sm font-semibold text-zinc-900">
|
||||
{course.name}
|
||||
</p>
|
||||
<div class="mt-3 grid gap-1 text-sm text-zinc-600">
|
||||
<div class="mt-1 grid gap-0.5 text-xs text-zinc-600">
|
||||
<p>课程号:{course.id}</p>
|
||||
<p>老师:{course.teacher}</p>
|
||||
<p>进度:{course.progress}</p>
|
||||
@@ -199,14 +232,16 @@ const CourseWorkspace = (props: CourseWorkspaceProps) => {
|
||||
<div
|
||||
class={
|
||||
compact()
|
||||
? "grid min-h-0 gap-3 xl:grid-rows-[minmax(0,1fr)_240px]"
|
||||
: "grid min-h-0 gap-4 xl:grid-rows-[minmax(0,1fr)_260px]"
|
||||
? "grid min-h-0 gap-1.5 grid-rows-[minmax(0,1fr)_176px] xl:grid-rows-[minmax(0,1fr)_188px]"
|
||||
: "grid min-h-0 gap-2 grid-rows-[minmax(0,1fr)_188px] xl:grid-rows-[minmax(0,1fr)_208px]"
|
||||
}
|
||||
>
|
||||
<div class="flex min-h-0 flex-col overflow-hidden rounded-3xl border border-zinc-200 bg-[linear-gradient(180deg,rgba(248,250,252,0.9),rgba(255,255,255,0.95))]">
|
||||
<div class="flex flex-wrap items-center justify-between gap-3 border-b border-zinc-200 px-4 py-3">
|
||||
<div class="flex min-h-0 flex-col overflow-hidden rounded-[18px] border border-zinc-200 bg-[linear-gradient(180deg,rgba(248,250,252,0.9),rgba(255,255,255,0.95))]">
|
||||
<div class="flex flex-wrap items-center justify-between gap-1.5 border-b border-zinc-200 px-2.5 py-2">
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-zinc-800">记录列表</p>
|
||||
<p class="text-xs font-semibold text-zinc-800 sm:text-sm">
|
||||
记录列表
|
||||
</p>
|
||||
<p class="mt-1 text-xs text-zinc-500">
|
||||
{props.selectedCourse
|
||||
? props.selectedCourse.name
|
||||
@@ -221,10 +256,10 @@ const CourseWorkspace = (props: CourseWorkspaceProps) => {
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<div class="flex flex-wrap items-center gap-1.5">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-xl border border-zinc-200 bg-white px-3 py-2 text-sm text-zinc-700 transition hover:bg-zinc-100 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
class="rounded-lg border border-zinc-200 bg-white px-2 py-1 text-xs text-zinc-700 transition hover:bg-zinc-100 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
disabled={
|
||||
!props.selectedCourse || props.isRefreshingRecords
|
||||
}
|
||||
@@ -234,7 +269,7 @@ const CourseWorkspace = (props: CourseWorkspaceProps) => {
|
||||
</button>
|
||||
|
||||
<select
|
||||
class="rounded-xl border border-zinc-200 bg-white px-3 py-2 text-sm transition outline-none focus:border-cyan-400"
|
||||
class="rounded-lg border border-zinc-200 bg-white px-2 py-1 text-xs transition outline-none focus:border-cyan-400"
|
||||
value={props.recordType}
|
||||
onChange={(event) =>
|
||||
props.onChangeRecordType(
|
||||
@@ -252,7 +287,7 @@ const CourseWorkspace = (props: CourseWorkspaceProps) => {
|
||||
<Show when={props.recordType === ""}>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-xl border border-cyan-200 bg-cyan-50 px-3 py-2 text-sm text-cyan-700 transition hover:bg-cyan-100"
|
||||
class="rounded-lg border border-cyan-200 bg-cyan-50 px-2 py-1 text-xs text-cyan-700 transition hover:bg-cyan-100"
|
||||
onClick={
|
||||
props.isRunningStudy
|
||||
? props.onStopStudy
|
||||
@@ -264,12 +299,40 @@ const CourseWorkspace = (props: CourseWorkspaceProps) => {
|
||||
</Show>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center justify-between gap-1.5 border-b border-zinc-200/70 px-2.5 py-1.5">
|
||||
<div class="flex flex-wrap items-center gap-1.5 text-xs">
|
||||
<button
|
||||
type="button"
|
||||
class={recordFilter() === "all" ? "rounded-full border border-zinc-200 bg-zinc-100 px-2.5 py-0.5 font-medium text-zinc-700" : "rounded-full border border-zinc-200 bg-white px-2.5 py-0.5 text-zinc-600 transition hover:bg-zinc-100"}
|
||||
onClick={() => setRecordFilter("all")}
|
||||
>
|
||||
全部 {recordStats().total}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class={recordFilter() === "unlearned" ? "rounded-full border border-amber-200 bg-amber-100 px-2.5 py-0.5 font-medium text-amber-700" : "rounded-full border border-amber-200 bg-white px-2.5 py-0.5 text-amber-700 transition hover:bg-amber-50"}
|
||||
onClick={() => setRecordFilter("unlearned")}
|
||||
>
|
||||
未学 {recordStats().unlearned}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class={recordFilter() === "learned" ? "rounded-full border border-emerald-200 bg-emerald-100 px-2.5 py-0.5 font-medium text-emerald-700" : "rounded-full border border-emerald-200 bg-white px-2.5 py-0.5 text-emerald-700 transition hover:bg-emerald-50"}
|
||||
onClick={() => setRecordFilter("learned")}
|
||||
>
|
||||
已学 {recordStats().learned}
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-xs text-zinc-500">
|
||||
当前筛选:{recordFilter() === "all" ? "全部" : recordFilter() === "unlearned" ? "未学" : "已学"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class={
|
||||
compact()
|
||||
? "min-h-0 flex-1 overflow-y-auto p-2.5"
|
||||
: "min-h-0 flex-1 overflow-y-auto p-3"
|
||||
? "min-h-0 flex-1 overflow-y-auto p-1.5"
|
||||
: "min-h-0 flex-1 overflow-y-auto p-2"
|
||||
}
|
||||
>
|
||||
<Show when={props.recordsLoading}>
|
||||
@@ -297,56 +360,78 @@ const CourseWorkspace = (props: CourseWorkspaceProps) => {
|
||||
!props.recordsLoading &&
|
||||
!props.recordError &&
|
||||
props.selectedCourse &&
|
||||
props.records.length === 0
|
||||
filteredRecords().length === 0
|
||||
}
|
||||
>
|
||||
<EmptyState>当前分类下没有记录。</EmptyState>
|
||||
<EmptyState>
|
||||
{props.records.length === 0
|
||||
? "当前分类下没有记录。"
|
||||
: "当前筛选下没有记录。"}
|
||||
</EmptyState>
|
||||
</Show>
|
||||
|
||||
<div class="flex flex-col gap-3">
|
||||
<For each={props.records}>
|
||||
{(record) => (
|
||||
<div
|
||||
class={
|
||||
compact()
|
||||
? "rounded-[20px] border border-zinc-200 bg-white px-3 py-3 shadow-sm"
|
||||
: "rounded-[22px] border border-zinc-200 bg-white px-4 py-4 shadow-sm"
|
||||
}
|
||||
>
|
||||
<div class="flex flex-wrap items-start justify-between gap-3">
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="truncate text-base font-semibold text-zinc-900">
|
||||
{record.name}
|
||||
</p>
|
||||
<p class="mt-1 text-sm text-zinc-500">
|
||||
记录 ID:{record.id} | 章节:{record.chapterId}
|
||||
</p>
|
||||
</div>
|
||||
<span class="rounded-full bg-zinc-100 px-3 py-1 text-xs font-medium text-zinc-700">
|
||||
{props.renderRecordState(record.state) ||
|
||||
"未知状态"}
|
||||
</span>
|
||||
</div>
|
||||
<div class={compact() ? "flex flex-col gap-1.5" : "flex flex-col gap-2"}>
|
||||
<For each={filteredRecords()}>
|
||||
{(record) => {
|
||||
const stateText =
|
||||
props.renderRecordState(record.state) || "未知状态";
|
||||
const learned =
|
||||
stateText.includes("已学") || record.progress === "1.00";
|
||||
|
||||
<div class="mt-4 grid gap-2 text-sm text-zinc-600 md:grid-cols-2 xl:grid-cols-3">
|
||||
<p>视频时长:{record.videoDuration}</p>
|
||||
<p>学习秒数:{record.duration}</p>
|
||||
<p>学习进度:{record.progress}</p>
|
||||
<p>开始时间:{record.beginTime || "-"}</p>
|
||||
<p>完成时间:{record.finalTime || "-"}</p>
|
||||
<p>查看次数:{record.viewCount}</p>
|
||||
return (
|
||||
<div
|
||||
class={
|
||||
learned
|
||||
? compact()
|
||||
? "rounded-[16px] border border-emerald-200 bg-emerald-50/35 px-2 py-1.5 shadow-sm"
|
||||
: "rounded-[18px] border border-emerald-200 bg-emerald-50/35 px-2.5 py-2.5 shadow-sm"
|
||||
: compact()
|
||||
? "rounded-[16px] border border-amber-200 bg-amber-50/35 px-2 py-1.5 shadow-sm"
|
||||
: "rounded-[18px] border border-amber-200 bg-amber-50/35 px-2.5 py-2.5 shadow-sm"
|
||||
}
|
||||
>
|
||||
<div class="flex flex-wrap items-start justify-between gap-2">
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="truncate text-sm font-semibold text-zinc-900">
|
||||
{record.name}
|
||||
</p>
|
||||
<p class="mt-1 text-xs text-zinc-500">
|
||||
记录 ID:{record.id} | 章节:{record.chapterId}
|
||||
</p>
|
||||
</div>
|
||||
<span
|
||||
class={
|
||||
learned
|
||||
? "rounded-full bg-emerald-100 px-3 py-1 text-xs font-medium text-emerald-700"
|
||||
: "rounded-full bg-amber-100 px-3 py-1 text-xs font-medium text-amber-700"
|
||||
}
|
||||
>
|
||||
{stateText}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 grid gap-1 text-xs text-zinc-600 md:grid-cols-2 xl:grid-cols-3">
|
||||
<p>视频时长:{record.videoDuration}</p>
|
||||
<p>学习秒数:{record.duration}</p>
|
||||
<p>学习进度:{record.progress}</p>
|
||||
<p>开始时间:{record.beginTime || "-"}</p>
|
||||
<p>完成时间:{record.finalTime || "-"}</p>
|
||||
<p>查看次数:{record.viewCount}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
);
|
||||
}}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex min-h-0 flex-col overflow-hidden rounded-3xl border border-zinc-200 bg-white">
|
||||
<div class="flex items-center justify-between border-b border-zinc-200 px-4 py-3">
|
||||
<div class="flex min-h-0 flex-col overflow-hidden rounded-[18px] border border-zinc-200 bg-white">
|
||||
<div class="flex items-center justify-between border-b border-zinc-200 px-2.5 py-2">
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-zinc-800">运行日志</p>
|
||||
<p class="text-xs font-semibold text-zinc-800 sm:text-sm">
|
||||
运行日志
|
||||
</p>
|
||||
<p class="mt-1 text-xs text-zinc-500">
|
||||
输出会自动滚动到最新位置
|
||||
</p>
|
||||
@@ -355,7 +440,7 @@ const CourseWorkspace = (props: CourseWorkspaceProps) => {
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-lg border border-zinc-200 px-3 py-1 text-xs text-zinc-700 transition hover:bg-zinc-100 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
class="rounded-lg border border-zinc-200 px-2 py-0.5 text-xs text-zinc-700 transition hover:bg-zinc-100 disabled:cursor-not-allowed disabled:opacity-60"
|
||||
disabled={props.isRefreshingLogs}
|
||||
onClick={props.onRefreshLogs}
|
||||
>
|
||||
@@ -363,7 +448,7 @@ const CourseWorkspace = (props: CourseWorkspaceProps) => {
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="rounded-lg border border-zinc-200 px-3 py-1 text-xs text-zinc-700 transition hover:bg-zinc-100"
|
||||
class="rounded-lg border border-zinc-200 px-2 py-0.5 text-xs text-zinc-700 transition hover:bg-zinc-100"
|
||||
onClick={props.onClearLogs}
|
||||
>
|
||||
清空日志
|
||||
@@ -373,13 +458,13 @@ const CourseWorkspace = (props: CourseWorkspaceProps) => {
|
||||
|
||||
<div
|
||||
ref={logContainerRef}
|
||||
class="min-h-0 flex-1 overflow-y-auto bg-zinc-950 px-4 py-3 font-mono text-emerald-300"
|
||||
class="min-h-0 flex-1 overflow-y-auto bg-zinc-950 px-2 py-1.5 font-mono text-emerald-300"
|
||||
>
|
||||
<Show
|
||||
when={props.studyLogs.length > 0}
|
||||
fallback={<p>暂无日志输出。</p>}
|
||||
>
|
||||
<div class="space-y-2">
|
||||
<div class="space-y-1">
|
||||
<For each={props.studyLogs}>
|
||||
{(log, index) => (
|
||||
<p>
|
||||
|
||||
Reference in New Issue
Block a user