add item stats
This commit is contained in:
parent
41016c184d
commit
b2ff5d8416
7 changed files with 221 additions and 95 deletions
|
@ -1,4 +1,4 @@
|
|||
use std::{path::PathBuf, time::Duration};
|
||||
use std::{collections::HashMap, path::PathBuf, time::Duration};
|
||||
|
||||
use sqlx::{Row, SqlitePool};
|
||||
use tauri::State;
|
||||
|
@ -18,10 +18,12 @@ pub struct SrsStats {
|
|||
total_items: u32,
|
||||
total_reviews: u32,
|
||||
|
||||
next_review: Option<i64>,
|
||||
grades: HashMap<u32, u32>,
|
||||
|
||||
/// Used to calculate average success
|
||||
num_success: u32,
|
||||
num_failure: u32,
|
||||
next_review: Option<i64>,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
@ -65,6 +67,21 @@ pub async fn get_srs_stats(db: State<'_, SrsDb>) -> Result<SrsStats, String> {
|
|||
.await
|
||||
.map_err(|err| err.to_string())?;
|
||||
|
||||
// current grades query
|
||||
let row3 = sqlx::query(
|
||||
r#"
|
||||
SELECT CurrentGrade, COUNT(*) FROM SrsEntrySet
|
||||
GROUP BY CurrentGrade
|
||||
"#,
|
||||
)
|
||||
.fetch_all(&db.0)
|
||||
.await
|
||||
.map_err(|err| err.to_string())?;
|
||||
let grades = row3
|
||||
.into_iter()
|
||||
.map(|row| (row.get::<u32, _>(0), row.get::<u32, _>(1)))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let next_review = row
|
||||
.try_get::<i64, _>("next_review")
|
||||
.ok()
|
||||
|
@ -78,6 +95,7 @@ pub async fn get_srs_stats(db: State<'_, SrsDb>) -> Result<SrsStats, String> {
|
|||
num_success: row.try_get("num_success").unwrap_or(0),
|
||||
num_failure: row.try_get("num_failure").unwrap_or(0),
|
||||
next_review,
|
||||
grades,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
40
src/components/DashboardItemStats.module.scss
Normal file
40
src/components/DashboardItemStats.module.scss
Normal file
|
@ -0,0 +1,40 @@
|
|||
.container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.group {
|
||||
flex-grow: 1;
|
||||
color: white;
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.groupHeader {
|
||||
h1 {
|
||||
font-size: 2.5em;
|
||||
text-shadow: 1px 1px black;
|
||||
}
|
||||
}
|
||||
|
||||
.groupLevels {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
justify-content: space-evenly;
|
||||
|
||||
h3 {
|
||||
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||
background-color: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
}
|
||||
|
||||
.level {
|
||||
flex-grow: 1;
|
||||
}
|
51
src/components/DashboardItemStats.tsx
Normal file
51
src/components/DashboardItemStats.tsx
Normal file
|
@ -0,0 +1,51 @@
|
|||
import srsLevels from "../data/srslevels.json";
|
||||
import { SrsStats } from "../panes/SrsPane";
|
||||
import styles from "./DashboardItemStats.module.scss";
|
||||
|
||||
export interface DashboardItemStatsProps {
|
||||
srsStats: SrsStats;
|
||||
}
|
||||
|
||||
const srsLevelsByGroups = new Map(
|
||||
srsLevels.groups.map((group) => [
|
||||
group,
|
||||
srsLevels.levels.filter((level) => level.group == group.name),
|
||||
]),
|
||||
);
|
||||
|
||||
export default function DashboardItemStats({ srsStats }: DashboardItemStatsProps) {
|
||||
const grades = new Map(Object.entries(srsStats.grades).map(([k, v]) => [parseInt(k), v]));
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{srsLevels.groups.map((group) => {
|
||||
const groupLevels = srsLevelsByGroups.get(group);
|
||||
if (!groupLevels) return null;
|
||||
|
||||
const groupCount = groupLevels
|
||||
.map((level) => grades.get(level.value) ?? 0)
|
||||
.reduce((a, b) => a + b);
|
||||
|
||||
return (
|
||||
<div style={{ backgroundColor: group.color }} className={styles.group} key={group.name}>
|
||||
<div className={styles.groupHeader}>
|
||||
<h1>{groupCount}</h1>
|
||||
|
||||
{group.name}
|
||||
</div>
|
||||
|
||||
<div className={styles.groupLevels}>
|
||||
{groupLevels.map((level) => (
|
||||
<div className={styles.level} key={level.name}>
|
||||
<h3>{level.name}</h3>
|
||||
|
||||
{grades.get(level.value)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -7,18 +7,10 @@ import { Link } from "react-router-dom";
|
|||
import ConditionalWrapper from "./utils/ConditionalWrapper";
|
||||
import ReactTimeago, { Formatter } from "react-timeago";
|
||||
import { isValid } from "date-fns";
|
||||
import { SrsStats } from "../panes/SrsPane";
|
||||
|
||||
interface SrsStats {
|
||||
reviews_available: number;
|
||||
|
||||
reviews_today: number;
|
||||
total_items: number;
|
||||
total_reviews: number;
|
||||
next_review: number;
|
||||
|
||||
/// Used to calculate average success
|
||||
num_success: number;
|
||||
num_failure: number;
|
||||
export interface DashboardReviewStatsProps {
|
||||
srsStats: SrsStats;
|
||||
}
|
||||
|
||||
interface Stat {
|
||||
|
@ -26,21 +18,7 @@ interface Stat {
|
|||
value: any;
|
||||
}
|
||||
|
||||
export default function DashboardReviewStats() {
|
||||
const {
|
||||
data: srsStats,
|
||||
error,
|
||||
isLoading,
|
||||
} = useSWR(["get_srs_stats"], ([command]) => invoke<SrsStats>(command));
|
||||
|
||||
if (!srsStats)
|
||||
return (
|
||||
<>
|
||||
{JSON.stringify([srsStats, error, isLoading])}
|
||||
Loading...
|
||||
</>
|
||||
);
|
||||
|
||||
export default function DashboardReviewStats({ srsStats }: DashboardReviewStatsProps) {
|
||||
const averageSuccess = srsStats.num_success / (srsStats.num_success + srsStats.num_failure || 1);
|
||||
const averageSuccessStr = `${Math.round(averageSuccess * 10000) / 100}%`;
|
||||
|
||||
|
|
|
@ -1,69 +1,78 @@
|
|||
[
|
||||
{
|
||||
"group": "Set In Stone",
|
||||
"name": "★",
|
||||
"value": 8,
|
||||
"delay": null,
|
||||
"color": "#1C1C1C"
|
||||
},
|
||||
{
|
||||
"groups": [
|
||||
{ "name": "Discovering", "color": "#1A814D" },
|
||||
{ "name": "Committing", "color": "#004E8E" },
|
||||
{ "name": "Bolstering", "color": "#5C1696" },
|
||||
{ "name": "Assimilating", "color": "#890062" },
|
||||
{ "name": "Set In Stone", "color": "#1C1C1C" }
|
||||
],
|
||||
"levels": [
|
||||
{
|
||||
"group": "Set In Stone",
|
||||
"name": "★",
|
||||
"value": 8,
|
||||
"delay": null,
|
||||
"color": "#1C1C1C"
|
||||
},
|
||||
|
||||
{
|
||||
"group": "Assimilating",
|
||||
"name": "A2",
|
||||
"value": 7,
|
||||
"delay": 10368000,
|
||||
"color": "#890062"
|
||||
},
|
||||
{
|
||||
"group": "Assimilating",
|
||||
"name": "A1",
|
||||
"value": 6,
|
||||
"delay": 2592000,
|
||||
"color": "#890062"
|
||||
},
|
||||
{
|
||||
"group": "Assimilating",
|
||||
"name": "A2",
|
||||
"value": 7,
|
||||
"delay": 10368000,
|
||||
"color": "#890062"
|
||||
},
|
||||
{
|
||||
"group": "Assimilating",
|
||||
"name": "A1",
|
||||
"value": 6,
|
||||
"delay": 2592000,
|
||||
"color": "#890062"
|
||||
},
|
||||
|
||||
{
|
||||
"group": "Bolstering",
|
||||
"name": "B2",
|
||||
"value": 5,
|
||||
"delay": 1209600,
|
||||
"color": "#5C1696"
|
||||
},
|
||||
{
|
||||
"group": "Bolstering",
|
||||
"name": "B1",
|
||||
"value": 4,
|
||||
"delay": 604800,
|
||||
"color": "#5C1696"
|
||||
},
|
||||
{
|
||||
"group": "Bolstering",
|
||||
"name": "B2",
|
||||
"value": 5,
|
||||
"delay": 1209600,
|
||||
"color": "#5C1696"
|
||||
},
|
||||
{
|
||||
"group": "Bolstering",
|
||||
"name": "B1",
|
||||
"value": 4,
|
||||
"delay": 604800,
|
||||
"color": "#5C1696"
|
||||
},
|
||||
|
||||
{
|
||||
"group": "Committing",
|
||||
"name": "C2",
|
||||
"value": 3,
|
||||
"delay": 259200,
|
||||
"color": "#004E8E"
|
||||
},
|
||||
{
|
||||
"group": "Committing",
|
||||
"name": "C1",
|
||||
"value": 2,
|
||||
"delay": 86400,
|
||||
"color": "#004E8E"
|
||||
},
|
||||
{
|
||||
"group": "Committing",
|
||||
"name": "C2",
|
||||
"value": 3,
|
||||
"delay": 259200,
|
||||
"color": "#004E8E"
|
||||
},
|
||||
{
|
||||
"group": "Committing",
|
||||
"name": "C1",
|
||||
"value": 2,
|
||||
"delay": 86400,
|
||||
"color": "#004E8E"
|
||||
},
|
||||
|
||||
{
|
||||
"group": "Discovering",
|
||||
"name": "D2",
|
||||
"value": 1,
|
||||
"delay": 28800,
|
||||
"color": "#1A814D"
|
||||
},
|
||||
{
|
||||
"group": "Discovering",
|
||||
"name": "D1",
|
||||
"value": 0,
|
||||
"delay": 14400,
|
||||
"color": "#1A814D"
|
||||
}
|
||||
]
|
||||
{
|
||||
"group": "Discovering",
|
||||
"name": "D2",
|
||||
"value": 1,
|
||||
"delay": 28800,
|
||||
"color": "#1A814D"
|
||||
},
|
||||
{
|
||||
"group": "Discovering",
|
||||
"name": "D1",
|
||||
"value": 0,
|
||||
"delay": 14400,
|
||||
"color": "#1A814D"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -16,7 +16,9 @@ export interface SrsLevel {
|
|||
}
|
||||
|
||||
import srsLevelMap from "../data/srslevels.json";
|
||||
export const srsLevels: Map<number, SrsLevel> = new Map(srsLevelMap.map((v) => [v.value, v]));
|
||||
export const srsLevels: Map<number, SrsLevel> = new Map(
|
||||
srsLevelMap.levels.map((v) => [v.value, v]),
|
||||
);
|
||||
|
||||
export interface SrsQuestionGroup {
|
||||
srsEntry: SrsEntry;
|
||||
|
|
|
@ -1,12 +1,40 @@
|
|||
import useSWR from "swr";
|
||||
import DashboardItemStats from "../components/DashboardItemStats";
|
||||
import DashboardReviewStats from "../components/DashboardReviewStats";
|
||||
import styles from "./SrsPane.module.scss";
|
||||
import { invoke } from "@tauri-apps/api";
|
||||
|
||||
export interface SrsStats {
|
||||
reviews_available: number;
|
||||
|
||||
reviews_today: number;
|
||||
total_items: number;
|
||||
total_reviews: number;
|
||||
|
||||
grades: { string: number };
|
||||
next_review: number;
|
||||
|
||||
/// Used to calculate average success
|
||||
num_success: number;
|
||||
num_failure: number;
|
||||
}
|
||||
|
||||
export function Component() {
|
||||
const {
|
||||
data: srsStats,
|
||||
error,
|
||||
isLoading,
|
||||
} = useSWR(["get_srs_stats"], ([command]) => invoke<SrsStats>(command));
|
||||
|
||||
if (!srsStats) return <>Loading...</>;
|
||||
|
||||
return (
|
||||
<main className={styles.main}>
|
||||
<DashboardReviewStats />
|
||||
<DashboardReviewStats srsStats={srsStats} />
|
||||
|
||||
<hr />
|
||||
|
||||
<DashboardItemStats srsStats={srsStats} />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue