More data on exams page

This commit is contained in:
micialware committed 2026-10-04 12:22:33 +03:00
1 parent 7ad44110c3
commit 496f246125
1 file changed
+104 -26
+104 -26
View File
@@ -1,6 +1,6 @@
use crate::app::AppState; use crate::app::AppState;
use crate::data::Id; use crate::data::Id;
use crate::data::entities::{DeckSettings, EntityHandle, Exam, ExamEntry}; use crate::data::entities::{DeckSettings, EntityHandle, Exam, ExamEntry, WordData};
use crate::data::repositories::{BatchedRepository, Repository}; use crate::data::repositories::{BatchedRepository, Repository};
use crate::navigation::Page::PreviousPage; use crate::navigation::Page::PreviousPage;
use crate::navigation::{NavigatedPage, Page, RootMessage}; use crate::navigation::{NavigatedPage, Page, RootMessage};
@@ -9,11 +9,18 @@ use crate::pages::exam::ExamState;
use crate::pages::exams::ExamsMessage::*; use crate::pages::exams::ExamsMessage::*;
use crate::pages::repetitions::Style; use crate::pages::repetitions::Style;
use crate::push_error; use crate::push_error;
use crate::styling::{BUTTON_RADIUS, DEFAULT_SPACING, HALF_SPACING, back_overlay, cta_button}; use crate::styling::{
BUTTON_RADIUS, DEFAULT_SPACING, HALF_SPACING, QUARTER_SPACING, back_overlay, cta_button,
};
use crate::{jl_button, push_error_task}; use crate::{jl_button, push_error_task};
use chrono::{Local, Utc}; use chrono::{Local, Utc};
use iced::widget::button::Status; use iced::widget::button::Status;
use iced::widget::{Column, button, center_x, column, container, row, rule, scrollable, space, text, tooltip}; use iced::widget::container::bordered_box;
use iced::widget::space::horizontal;
use iced::widget::{
Column, button, center, center_x, column, container, row, rule, scrollable, space, text,
tooltip,
};
use iced::{Element, Task}; use iced::{Element, Task};
use iced_core::Alignment::*; use iced_core::Alignment::*;
use iced_core::Length::{Fill, FillPortion}; use iced_core::Length::{Fill, FillPortion};
@@ -46,7 +53,11 @@ impl NavigatedPage<ExamsMessage> for ExamsState {
if let Some(selected_exam_id) = self.selected_exam_id if let Some(selected_exam_id) = self.selected_exam_id
&& GotoExam == *message && GotoExam == *message
{ {
return Some(Page::Exam(ExamState::new(selected_exam_id.id, self.deck.clone(), &self.state))); return Some(Page::Exam(ExamState::new(
selected_exam_id.id,
self.deck.clone(),
&self.state,
)));
} }
None None
} }
@@ -90,7 +101,7 @@ impl NavigatedPage<ExamsMessage> for ExamsState {
back_overlay( back_overlay(
column![ column![
text(format!("Экзамены для колоды {}", self.deck.name)), text(format!("Экзамены для колоды {}", self.deck.name)),
row![row![ row![
column![ column![
scrollable(self.exams_list()).height(Fill), scrollable(self.exams_list()).height(Fill),
jl_button!("Добавить").width(Fill).on_press(CreateExam), jl_button!("Добавить").width(Fill).on_press(CreateExam),
@@ -98,7 +109,7 @@ impl NavigatedPage<ExamsMessage> for ExamsState {
.spacing(DEFAULT_SPACING) .spacing(DEFAULT_SPACING)
.width(FillPortion(1)), .width(FillPortion(1)),
self.selected_exam_view(), self.selected_exam_view(),
]] ].spacing(DEFAULT_SPACING)
] ]
.into(), .into(),
Back, Back,
@@ -172,23 +183,33 @@ impl ExamsState {
for (handle, deck) in self.exams.iter() { for (handle, deck) in self.exams.iter() {
column = column.push( column = column.push(
jl_button!(text!("{}", &deck.timestamp.with_timezone(&Local).format("%d/%m/%Y %H:%M"))) jl_button!(text!(
.on_press_with(move || SelectExam(*handle)) "{}",
.style(move |_x: &Theme, status: Status| Style { &deck
background: if status == Status::Hovered { .timestamp
Some(Background::Color(Color::WHITE.scale_alpha(0.2))) .with_timezone(&Local)
} else { .format("%d/%m/%Y %H:%M")
None ))
}, .on_press_with(move || SelectExam(*handle))
text_color: if deck.finished() { _x.palette().success } else { _x.palette().primary}, .style(move |_x: &Theme, status: Status| Style {
border: Border { background: if status == Status::Hovered {
color: Default::default(), Some(Background::Color(Color::WHITE.scale_alpha(0.2)))
width: 0.0, } else {
radius: BUTTON_RADIUS.into(), None
}, },
shadow: Shadow::default(), text_color: if deck.finished() {
snap: false, _x.palette().success
}), } else {
_x.palette().primary
},
border: Border {
color: Default::default(),
width: 0.0,
radius: BUTTON_RADIUS.into(),
},
shadow: Shadow::default(),
snap: false,
}),
); );
} }
@@ -199,7 +220,9 @@ impl ExamsState {
return column![ return column![
center_x(text!( center_x(text!(
"Экзамен от {}", "Экзамен от {}",
exam.timestamp.with_timezone(&Local).format("%d/%m/%Y %H:%M") exam.timestamp
.with_timezone(&Local)
.format("%d/%m/%Y %H:%M")
)), )),
row![text!("{} карточек", exam.total), Self::exam_bar(exam)] row![text!("{} карточек", exam.total), Self::exam_bar(exam)]
.height(30) .height(30)
@@ -284,7 +307,35 @@ impl ExamsState {
} }
fn exam_finished_view(&self, exam: &ExamExtended) -> Element<'_, ExamsMessage> { fn exam_finished_view(&self, exam: &ExamExtended) -> Element<'_, ExamsMessage> {
column![].spacing(DEFAULT_SPACING).into() let result = (exam.answered as f64 / exam.total as f64 * 1000.0).round() / 10.0;
let words_repo = self.state.dictionary.read();
column![
center_x(text!("Итоговый результат: {}%", result).size(28)),
row![
Self::words_list(
"Правильно",
exam.answered_list
.iter()
.map(|handle| words_repo.get_ref_static(*handle).unwrap())
.collect::<Vec<_>>()
.as_slice()
)
.width(Fill),
Self::words_list(
"Не получилось",
exam.unknows_list
.iter()
.map(|handle| words_repo.get_ref_static(*handle).unwrap())
.collect::<Vec<_>>()
.as_slice()
)
.width(Fill)
]
.width(Fill)
.spacing(QUARTER_SPACING)
]
.spacing(DEFAULT_SPACING)
.into()
} }
fn exam_ongoing_view(&self, exam: &ExamExtended) -> Element<'_, ExamsMessage> { fn exam_ongoing_view(&self, exam: &ExamExtended) -> Element<'_, ExamsMessage> {
@@ -295,6 +346,26 @@ impl ExamsState {
) )
.into() .into()
} }
fn words_list<'a>(name: &str, words: &[&WordData]) -> Column<'a, ExamsMessage> {
let mut column = column![];
for word in words {
let container = container(
column![text!("{}", word.key), text!("{}", word.value)].padding(QUARTER_SPACING),
)
.width(Fill)
.style(bordered_box);
column = column.push(container);
}
column![
text!("{}", name),
scrollable(row![
column.spacing(QUARTER_SPACING),
horizontal().width(DEFAULT_SPACING)
])
]
.align_x(Center)
}
} }
struct ExamExtended { struct ExamExtended {
@@ -302,6 +373,8 @@ struct ExamExtended {
total: u32, total: u32,
answered: u32, answered: u32,
unknown: u32, unknown: u32,
answered_list: Vec<EntityHandle<WordData>>,
unknows_list: Vec<EntityHandle<WordData>>,
} }
impl ExamExtended { impl ExamExtended {
@@ -310,6 +383,8 @@ impl ExamExtended {
let mut total = 0; let mut total = 0;
let mut answered = 0; let mut answered = 0;
let mut unknown = 0; let mut unknown = 0;
let mut answered_list = Vec::with_capacity(128);
let mut unknows_list = Vec::with_capacity(128);
entries entries
.iter() .iter()
.filter(|e| e.exam_id == exam.id) .filter(|e| e.exam_id == exam.id)
@@ -320,8 +395,10 @@ impl ExamExtended {
Some(s) => { Some(s) => {
if s { if s {
answered += 1; answered += 1;
answered_list.push(EntityHandle::from_id(e.word_id));
} else { } else {
unknown += 1; unknown += 1;
unknows_list.push(EntityHandle::from_id(e.word_id));
} }
} }
} }
@@ -329,10 +406,11 @@ impl ExamExtended {
Self { Self {
exam, exam,
total, total,
answered, answered,
unknown, unknown,
answered_list,
unknows_list,
} }
} }
fn solved(&self) -> u32 { fn solved(&self) -> u32 {