This commit is contained in:
Michael Zhang 2023-06-11 15:08:17 -05:00
parent 3080602727
commit aae088ddb9
6 changed files with 115 additions and 18 deletions

21
package-lock.json generated
View file

@ -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",

View file

@ -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",

View file

@ -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),
}) })
} }

View file

@ -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",

View file

@ -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 (
<Stat> <GridItem>
<StatLabel>{stat.label}</StatLabel> <Stat>
<StatNumber>{stat.value}</StatNumber> <StatLabel>{stat.label}</StatLabel>
</Stat> <StatNumber>{stat.value}</StatNumber>
</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>
</> </>
); );
} }

View file

@ -0,0 +1,3 @@
export default function SrsReviewPane() {
return <>review</>;
}