Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion frontend/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import "../../static/materials/repeating_queue.css";
import "../../static/materials/add_material.css";
import "../../static/notes/notes.css";
import "../../static/notes/add_note.css";
import "../../static/reading_log/add_log_record.css";
import "../../static/cards/cards_list.css";
import "../../static/cards/add_card.css";
import "../../static/system/system.css";
Expand Down
64 changes: 31 additions & 33 deletions frontend/src/pages/reading_log/AddReadingLogPage.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useEffect, useRef, useState } from "react";
import {useEffect, useMemo, useState} from "react";
import { useSearchParams, useLocation, useNavigate } from "react-router-dom";

import { apiFetch } from "../../api/readingLog";
import { apiFetch as materialsApiFetch } from "../../api/materials.ts";
import { CelebrateButton } from "../../components/CelebrateButton";
import { useAltchHotkeys } from "../../hooks/useAltchHotkeys";
import {
GetMaterialCompletionInfoResponse,
GetMaterialReadingNowResponse,
ListReadingMaterialsTitlesResponse
ListMaterialsTitlesResponse,
} from "../../types.ts";
import {isUuid} from "../../utils/isUuid.ts";
import {ComboboxInput, ComboboxList, ComboboxRoot} from "../../components/Combobox.tsx";

export function AddReadingLogPage() {
const [searchParams] = useSearchParams();
const initialMaterial = searchParams.get("material_id");

const contentRef = useRef<HTMLTextAreaElement>(null);
useAltchHotkeys(contentRef);

const [materialId, setMaterialId] = useState(initialMaterial);
const [date, setDate] = useState("");
const [count, setCount] = useState("");
Expand All @@ -30,20 +28,20 @@ export function AddReadingLogPage() {
const from = location.state?.from || "/materials/reading";

const getMaterialReadingNow = useQuery({
queryKey: ["material_reading_now"],
queryKey: ["materials", "reading_now"],
queryFn: () =>
apiFetch<GetMaterialReadingNowResponse>("/material-reading-now"),
});

const completionQ = useQuery({
queryKey: ["material", materialId, "completion-info"],
queryFn: () => apiFetch<GetMaterialCompletionInfoResponse>(`/${materialId}/completion-info`),
queryKey: ["materials", materialId, "completion-info"],
queryFn: () => materialsApiFetch<GetMaterialCompletionInfoResponse>(`/${materialId}/completion-info`),
enabled: !!materialId && isUuid(materialId),
});

const readingMaterialsTitlesQ = useQuery({
queryKey: ["reading_materials_titles"],
queryFn: () => apiFetch<ListReadingMaterialsTitlesResponse>("/reading-materials-titles"),
queryKey: ["materials", "reading_titles"],
queryFn: () => materialsApiFetch<ListMaterialsTitlesResponse>("/reading-titles"),
staleTime: 5 * 60 * 1000,
});

Expand Down Expand Up @@ -86,21 +84,29 @@ export function AddReadingLogPage() {

void qc.invalidateQueries({ queryKey: ["reading_logs", "list"] });
void qc.invalidateQueries({ queryKey: ["materials", "reading"] });
void qc.invalidateQueries({ queryKey: ["materials", materialId, "completion-info"] });
navigate(from);
},
onError: (e: Error) => {
setError(e.message);
},
});

const titles = readingMaterialsTitles?.items ?? {};
const materialOptions = useMemo(() => {
return Object.keys(titles).sort((a, b) =>
(titles[a] ?? "").localeCompare(titles[b] ?? ""),
);
}, [titles]);

if (getMaterialReadingNow.isLoading || completionQ.isLoading || readingMaterialsTitlesQ.isLoading) {
return <p>Loading…</p>;
}
const hasError = getMaterialReadingNow.error || readingMaterialsTitlesQ.error || completionQ.error;
if (hasError) {
return (
<p className="error">
{(hasError as Error).message || "Не удалось загрузить данные"}
{(hasError as Error).message || "Failed to load data"}
</p>
);
}
Expand All @@ -117,27 +123,19 @@ export function AddReadingLogPage() {
>
<fieldset className="fieldset">
<legend className="legend"> Add reading log </legend>
<input
id="input_material_id"
className="input input-datalist"
list="materials"
placeholder="Choose a material"
value={materialId || ""}
title="ID of the material"
onChange={(e) => {
setMaterialId(e.target.value);
}}
/>
{/*todo: rewrite with combobox*/}
<datalist id="materials">
{Object.entries(readingMaterialsTitles?.items ?? {})
.sort((a, b) => a[1].localeCompare(b[1]))
.map(([id, t]) => (
<option key={id} value={id}>
«{t}»
</option>
))}
</datalist>
<ComboboxRoot
options={materialOptions}
getOptionLabel={(id) => titles[id] || id}
value={materialId || ""}
onChange={setMaterialId}
>
<ComboboxInput
placeholder="Choose a material"
className="input"
title="ID of the material"
/>
<ComboboxList />
</ComboboxRoot>

<p
className="little-text"
Expand Down
57 changes: 32 additions & 25 deletions frontend/src/pages/reading_log/ListReadingLogsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,40 @@
import { useMemo } from "react";
import { useQuery } from "@tanstack/react-query";
import { useSearchParams } from "react-router-dom";

import { apiFetch } from "../../api/readingLog.ts";
import { apiFetch, buildQuery } from "../../api/readingLog.ts";
import { apiFetch as materialsApiFetch } from "../../api/materials.ts";
import {ListMaterialsTitlesResponse, ListReadingLogsResponse} from "../../types.ts";

import {ComboboxInput, ComboboxList, ComboboxRoot} from "../../components/Combobox.tsx";

export function ListReadingLogsPage() {
const [searchParams, setSearchParams] = useSearchParams();
const materialId = searchParams.get("material_id") ?? "";

const materialsTitlesQ = useQuery({
queryKey: ["materials", "reading_logs", "titles"],
queryFn: () => apiFetch<ListMaterialsTitlesResponse>("/materials-titles"),
queryKey: ["materials", "read_titles"],
queryFn: () => materialsApiFetch<ListMaterialsTitlesResponse>("/read-titles"),
staleTime: 5 * 60 * 1000,
});

const logsQ = useQuery({
queryKey: ["logs", { materialId }],
queryFn: () => {
const params = materialId ? `?material_id=${materialId}` : '';
return apiFetch<ListReadingLogsResponse>(`/${params}`);
return apiFetch<ListReadingLogsResponse>(`/${buildQuery({material_id: materialId || undefined})}`);
},
});

const materialsTitles = materialsTitlesQ.data?.items ?? {};
const data = logsQ.data?.items ?? [];

const materialOptions = useMemo(() => {
return Object.keys(materialsTitles).sort((a, b) =>
(materialsTitles[a] ?? "").localeCompare(materialsTitles[b] ?? ""),
);
}, [materialsTitlesQ]);

if (logsQ.isLoading || materialsTitlesQ.isLoading) {
return <p>Загрузка...</p>;
return <p>Loading...</p>;
}

if (logsQ.error || materialsTitlesQ.error) {
Expand All @@ -53,24 +60,24 @@ export function ListReadingLogsPage() {
}}
>

<input
className="input"
list="materials"
name="material_id"
defaultValue={materialId}
placeholder="Choose a material"
/>
{/*TODO: rewrite with combobox*/}
<datalist id="materials">
{Object.entries(materialsTitles)
.sort((a, b) => a[1].localeCompare(b[1]))
.map(([id, title]) => (
<option key={id} value={id}>
{" "}
«{title}»{" "}
</option>
))}
</datalist>
<ComboboxRoot
options={materialOptions}
getOptionLabel={(id) => materialsTitles[id] || id}
value={materialId}
onChange={(e) => {
const next = new URLSearchParams();
next.set("material_id", e);
setSearchParams(next);
}}
>
<ComboboxInput
placeholder="Choose a material"
className="input"
title="ID of the material"
/>
<ComboboxList />
</ComboboxRoot>

<button type="submit" className="submit-button">
{" "}
Search{" "}
Expand Down
4 changes: 0 additions & 4 deletions frontend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,6 @@ export type GetMaterialResponse = {
material: GetMaterialItem;
};

export type ListReadingMaterialsTitlesResponse = {
items: Record<string, string>;
}

export type GetMaterialCompletionInfoResponse = {
material_pages: number;
material_type: MaterialType;
Expand Down
20 changes: 0 additions & 20 deletions static/js/reading_log.js

This file was deleted.

7 changes: 0 additions & 7 deletions static/reading_log/add_log_record.css

This file was deleted.

40 changes: 0 additions & 40 deletions templates/reading_log/add_log_record.html

This file was deleted.

51 changes: 0 additions & 51 deletions templates/reading_log/reading_log.html

This file was deleted.

27 changes: 1 addition & 26 deletions tests/test_reading_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,35 +24,10 @@ def test_safe_list_get(lst, index, default, value):
assert db._safe_list_get(lst, index, default) == value


@pytest.mark.skip
async def test_get_mean_materials_read_pages():
# TODO: zero material
stat = await db.get_mean_materials_read_pages()

stmt = sa.select(models.ReadingLog.c.material_id, models.ReadingLog.c.count)

async with database.session() as ses:
result = (await ses.execute(stmt)).all()

expected_stat = {}
for material_id, count in result:
count = Decimal(count)
expected_stat[material_id] = [*expected_stat.get(material_id, []), count]

expected_result = {
material_id: round(statistics.mean(counts), 2)
for material_id, counts in expected_stat.items()
}

# not zeros
assert all(stat.values())
assert expected_result == stat


async def test_get_log_records():
stmt = sa.select(sa.func.count(1)).select_from(models.ReadingLog)

log_records = await db.get_log_records()
log_records = await db.list_log_records()

async with database.session() as ses:
expected_res_count = await ses.scalar(stmt)
Expand Down
Loading
Loading