stats
This commit is contained in:
parent
3080602727
commit
aae088ddb9
6 changed files with 115 additions and 18 deletions
21
package-lock.json
generated
21
package-lock.json
generated
|
@ -8,6 +8,7 @@
|
||||||
"name": "houhou",
|
"name": "houhou",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@chakra-ui/icons": "^2.0.19",
|
||||||
"@chakra-ui/react": "^2.7.0",
|
"@chakra-ui/react": "^2.7.0",
|
||||||
"@emotion/react": "^11.11.1",
|
"@emotion/react": "^11.11.1",
|
||||||
"@emotion/styled": "^11.11.0",
|
"@emotion/styled": "^11.11.0",
|
||||||
|
@ -682,6 +683,18 @@
|
||||||
"react": ">=18"
|
"react": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@chakra-ui/icons": {
|
||||||
|
"version": "2.0.19",
|
||||||
|
"resolved": "https://registry.npmjs.org/@chakra-ui/icons/-/icons-2.0.19.tgz",
|
||||||
|
"integrity": "sha512-0A6U1ZBZhLIxh3QgdjuvIEhAZi3B9v8g6Qvlfa3mu6vSnXQn2CHBZXmJwxpXxO40NK/2gj/gKXrLeUaFR6H/Qw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@chakra-ui/icon": "3.0.16"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@chakra-ui/system": ">=2.0.0",
|
||||||
|
"react": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@chakra-ui/image": {
|
"node_modules/@chakra-ui/image": {
|
||||||
"version": "2.0.16",
|
"version": "2.0.16",
|
||||||
"resolved": "https://registry.npmjs.org/@chakra-ui/image/-/image-2.0.16.tgz",
|
"resolved": "https://registry.npmjs.org/@chakra-ui/image/-/image-2.0.16.tgz",
|
||||||
|
@ -4475,6 +4488,14 @@
|
||||||
"@chakra-ui/shared-utils": "2.0.5"
|
"@chakra-ui/shared-utils": "2.0.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@chakra-ui/icons": {
|
||||||
|
"version": "2.0.19",
|
||||||
|
"resolved": "https://registry.npmjs.org/@chakra-ui/icons/-/icons-2.0.19.tgz",
|
||||||
|
"integrity": "sha512-0A6U1ZBZhLIxh3QgdjuvIEhAZi3B9v8g6Qvlfa3mu6vSnXQn2CHBZXmJwxpXxO40NK/2gj/gKXrLeUaFR6H/Qw==",
|
||||||
|
"requires": {
|
||||||
|
"@chakra-ui/icon": "3.0.16"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@chakra-ui/image": {
|
"@chakra-ui/image": {
|
||||||
"version": "2.0.16",
|
"version": "2.0.16",
|
||||||
"resolved": "https://registry.npmjs.org/@chakra-ui/image/-/image-2.0.16.tgz",
|
"resolved": "https://registry.npmjs.org/@chakra-ui/image/-/image-2.0.16.tgz",
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
"tauri": "tauri"
|
"tauri": "tauri"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@chakra-ui/icons": "^2.0.19",
|
||||||
"@chakra-ui/react": "^2.7.0",
|
"@chakra-ui/react": "^2.7.0",
|
||||||
"@emotion/react": "^11.11.1",
|
"@emotion/react": "^11.11.1",
|
||||||
"@emotion/styled": "^11.11.0",
|
"@emotion/styled": "^11.11.0",
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
use std::{
|
||||||
|
ops::Add,
|
||||||
|
time::{Duration, Instant, SystemTime, UNIX_EPOCH},
|
||||||
|
};
|
||||||
|
|
||||||
use sqlx::{Row, SqlitePool};
|
use sqlx::{Row, SqlitePool};
|
||||||
use tauri::State;
|
use tauri::State;
|
||||||
|
|
||||||
|
@ -18,6 +23,7 @@ pub struct SrsStats {
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn get_srs_stats(db: State<'_, SrsDb>) -> Result<SrsStats, String> {
|
pub async fn get_srs_stats(db: State<'_, SrsDb>) -> Result<SrsStats, String> {
|
||||||
|
// counts query
|
||||||
let row = sqlx::query(
|
let row = sqlx::query(
|
||||||
r#"
|
r#"
|
||||||
SELECT
|
SELECT
|
||||||
|
@ -31,12 +37,45 @@ pub async fn get_srs_stats(db: State<'_, SrsDb>) -> Result<SrsStats, String> {
|
||||||
.await
|
.await
|
||||||
.map_err(|err| err.to_string())?;
|
.map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
|
// reviews query
|
||||||
|
let utc_now = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.map_err(|err| err.to_string())?
|
||||||
|
.as_secs() as i64
|
||||||
|
* 1000000000;
|
||||||
|
let utc_tomorrow = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.map_err(|err| err.to_string())?
|
||||||
|
.add(Duration::from_secs(60 * 60 * 24))
|
||||||
|
.as_secs() as i64
|
||||||
|
* 1000000000;
|
||||||
|
let row2 = sqlx::query(
|
||||||
|
r#"
|
||||||
|
SELECT COUNT(*) AS reviews
|
||||||
|
FROM SrsEntrySet
|
||||||
|
WHERE
|
||||||
|
SuspensionDate IS NULL
|
||||||
|
AND NextAnswerDate <= ?
|
||||||
|
UNION ALL
|
||||||
|
SELECT COUNT(*) AS reviews
|
||||||
|
FROM SrsEntrySet
|
||||||
|
WHERE
|
||||||
|
SuspensionDate IS NULL
|
||||||
|
AND NextAnswerDate <= ?
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(&utc_now)
|
||||||
|
.bind(&utc_tomorrow)
|
||||||
|
.fetch_all(&db.0)
|
||||||
|
.await
|
||||||
|
.map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
Ok(SrsStats {
|
Ok(SrsStats {
|
||||||
reviews_available: 0,
|
reviews_available: row2[0].get("reviews"),
|
||||||
reviews_today: 0,
|
reviews_today: row2[1].get("reviews"),
|
||||||
total_items: row.get("total_items"),
|
total_items: row.try_get("total_items").unwrap_or(0),
|
||||||
total_reviews: 0,
|
total_reviews: 0,
|
||||||
num_success: row.get("num_success"),
|
num_success: row.try_get("num_success").unwrap_or(0),
|
||||||
num_failure: row.get("num_failure"),
|
num_failure: row.try_get("num_failure").unwrap_or(0),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
20
src/App.tsx
20
src/App.tsx
|
@ -11,6 +11,7 @@ import SettingsPane from "./panes/SettingsPane";
|
||||||
import { StrictMode } from "react";
|
import { StrictMode } from "react";
|
||||||
|
|
||||||
import styles from "./App.module.scss";
|
import styles from "./App.module.scss";
|
||||||
|
import SrsReviewPane from "./panes/SrsReviewPane";
|
||||||
|
|
||||||
function Layout() {
|
function Layout() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
@ -45,18 +46,19 @@ export default function App() {
|
||||||
<Route path="/" element={<Layout />}>
|
<Route path="/" element={<Layout />}>
|
||||||
{navLinks.flatMap((route, idx) => {
|
{navLinks.flatMap((route, idx) => {
|
||||||
if (route.subPaths) {
|
if (route.subPaths) {
|
||||||
return route.subPaths.map((subRoute, idx) => {
|
return route.subPaths.map((subRoute, idx2) => {
|
||||||
return (
|
return (
|
||||||
<Route
|
<Route
|
||||||
key={`${route.key}-${subRoute.key}`}
|
key={`${route.key}-${subRoute.key}`}
|
||||||
|
index={idx + idx2 == 0}
|
||||||
path={subRoute.path}
|
path={subRoute.path}
|
||||||
element={route.element}
|
element={subRoute.element ?? route.element}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<Route key={route.path} index={idx === 0} path={route.path} element={route.element} />
|
<Route key={route.path} index={idx == 0} path={route.path} element={route.element} />
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
|
@ -74,8 +76,16 @@ export default function App() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const navLinks = [
|
const navLinks = [
|
||||||
{ key: "home", path: "/", title: "Home", element: <HomePane /> },
|
// { key: "home", path: "/", title: "Home", element: <HomePane /> },
|
||||||
{ key: "srs", path: "/srs", title: "SRS", element: <SrsPane /> },
|
{
|
||||||
|
key: "srs",
|
||||||
|
title: "SRS",
|
||||||
|
element: <SrsPane />,
|
||||||
|
subPaths: [
|
||||||
|
{ key: "index", path: "/srs" },
|
||||||
|
{ key: "review", path: "/srs/review", element: <SrsReviewPane /> },
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: "kanji",
|
key: "kanji",
|
||||||
title: "Kanji",
|
title: "Kanji",
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import {
|
import {
|
||||||
|
Button,
|
||||||
Grid,
|
Grid,
|
||||||
GridItem,
|
GridItem,
|
||||||
Stat,
|
Stat,
|
||||||
|
@ -8,9 +9,11 @@ import {
|
||||||
StatLabel,
|
StatLabel,
|
||||||
StatNumber,
|
StatNumber,
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
|
import { ArrowRightIcon } from "@chakra-ui/icons";
|
||||||
import styles from "./DashboardReviewStats.module.scss";
|
import styles from "./DashboardReviewStats.module.scss";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
import { invoke } from "@tauri-apps/api/tauri";
|
import { invoke } from "@tauri-apps/api/tauri";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
interface SrsStats {
|
interface SrsStats {
|
||||||
reviews_available: number;
|
reviews_available: number;
|
||||||
|
@ -31,28 +34,48 @@ export default function DashboardReviewStats() {
|
||||||
isLoading,
|
isLoading,
|
||||||
} = useSWR(["get_srs_stats"], ([command]) => invoke<SrsStats>(command));
|
} = useSWR(["get_srs_stats"], ([command]) => invoke<SrsStats>(command));
|
||||||
|
|
||||||
if (!srsStats) return <>Loading...</>;
|
if (!srsStats)
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{JSON.stringify([srsStats, error, isLoading])}
|
||||||
|
Loading...
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
const averageSuccess = srsStats.num_success / (srsStats.num_success + srsStats.num_failure);
|
const averageSuccess = srsStats.num_success / (srsStats.num_success + srsStats.num_failure);
|
||||||
const averageSuccessStr = `${Math.round(averageSuccess * 10000) / 100}%`;
|
const averageSuccessStr = `${Math.round(averageSuccess * 10000) / 100}%`;
|
||||||
|
|
||||||
const generateStat = (stat) => {
|
const generateStat = (stat) => {
|
||||||
return (
|
return (
|
||||||
|
<GridItem>
|
||||||
<Stat>
|
<Stat>
|
||||||
<StatLabel>{stat.label}</StatLabel>
|
<StatLabel>{stat.label}</StatLabel>
|
||||||
<StatNumber>{stat.value}</StatNumber>
|
<StatNumber>{stat.value}</StatNumber>
|
||||||
</Stat>
|
</Stat>
|
||||||
|
</GridItem>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* JSON.stringify([srsStats, error, isLoading]) */}
|
<Grid templateColumns="2fr 1fr 1fr" templateRows="1fr 1fr">
|
||||||
|
<GridItem rowSpan={2}>
|
||||||
|
<Stat>
|
||||||
|
<StatLabel>reviews available</StatLabel>
|
||||||
|
<StatNumber>{srsStats.reviews_available}</StatNumber>
|
||||||
|
<Link to="/srs/review">
|
||||||
|
<Button disabled={srsStats.reviews_available == 0} colorScheme="blue">
|
||||||
|
Start reviewing <ArrowRightIcon marginLeft={3} />
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</Stat>
|
||||||
|
</GridItem>
|
||||||
|
|
||||||
<StatGroup>
|
{generateStat({ label: "reviews available", value: srsStats.reviews_available })}
|
||||||
|
{generateStat({ label: "reviews today", value: srsStats.reviews_today })}
|
||||||
{generateStat({ label: "total items", value: srsStats.total_items })}
|
{generateStat({ label: "total items", value: srsStats.total_items })}
|
||||||
{generateStat({ label: "average success", value: averageSuccessStr })}
|
{generateStat({ label: "average success", value: averageSuccessStr })}
|
||||||
</StatGroup>
|
</Grid>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
3
src/panes/SrsReviewPane.tsx
Normal file
3
src/panes/SrsReviewPane.tsx
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export default function SrsReviewPane() {
|
||||||
|
return <>review</>;
|
||||||
|
}
|
Loading…
Reference in a new issue