| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421 |
- use eframe::egui;
- use crate::api::ApiClient;
- use crate::config::{KioskFilterSettings, KioskUiSettings};
- use crate::models::{QueryRequest, OrderBy, UserInfo};
- use serde::Deserialize;
- use std::collections::HashMap;
- #[derive(Debug, Clone, Deserialize)]
- pub struct KioskUser {
- pub id: i32,
- pub username: String,
- pub name: String,
- pub role_id: i32,
- }
- #[derive(Debug, Clone, Deserialize)]
- pub struct KioskRole {
- pub id: i32,
- pub name: String,
- }
- pub enum LoginResult {
- None,
- Success(UserInfo, String),
- }
- pub struct KioskLoginView {
- users: Vec<KioskUser>,
- roles: HashMap<i32, String>,
- selected_user: Option<KioskUser>,
- pin_input: String,
- rfid_input: String,
- error_message: Option<String>,
- is_loading: bool,
- filter_settings: KioskFilterSettings,
- ui_settings: KioskUiSettings,
- is_rfid_mode: bool,
- }
- impl KioskLoginView {
- pub fn reset(&mut self) {
- self.selected_user = None;
- self.pin_input.clear();
- self.rfid_input.clear();
- self.error_message = None;
- self.is_rfid_mode = false;
- }
- pub fn new(filter_settings: KioskFilterSettings, ui_settings: KioskUiSettings) -> Self {
- Self {
- users: Vec::new(),
- roles: HashMap::new(),
- selected_user: None,
- pin_input: String::new(),
- rfid_input: String::new(),
- error_message: None,
- is_loading: false,
- filter_settings,
- ui_settings,
- is_rfid_mode: false,
- }
- }
- pub fn refresh_users(&mut self, client: &ApiClient, current_user: Option<&UserInfo>) {
- self.is_loading = true;
-
- // Fetch Roles first if needed for filtering
- if self.filter_settings.role_whitelist_enabled || self.filter_settings.role_blacklist_enabled {
- let role_req = QueryRequest {
- action: "select".to_string(),
- table: "roles".to_string(),
- columns: Some(vec!["id".to_string(), "name".to_string()]),
- data: None,
- r#where: None,
- filter: None,
- order_by: None,
- limit: Some(100),
- offset: None,
- joins: None,
- };
-
- if let Ok(response) = client.query(&role_req) {
- if response.success {
- if let Some(data) = response.data {
- if let Ok(roles) = serde_json::from_value::<Vec<KioskRole>>(data) {
- self.roles = roles.into_iter().map(|r| (r.id, r.name)).collect();
- }
- }
- }
- }
- }
- let request = QueryRequest {
- action: "select".to_string(),
- table: "users".to_string(),
- columns: Some(vec!["id".to_string(), "username".to_string(), "name".to_string(), "role_id".to_string()]),
- data: None,
- r#where: None,
- filter: None,
- order_by: Some(vec![OrderBy {
- column: "name".to_string(),
- direction: "ASC".to_string(),
- }]),
- limit: Some(100),
- offset: None,
- joins: None,
- };
- match client.query(&request) {
- Ok(response) => {
- if response.success {
- if let Some(data) = response.data {
- match serde_json::from_value::<Vec<KioskUser>>(data) {
- Ok(mut users) => {
- // Apply filters
- users.retain(|u| {
- // Hide self
- if self.filter_settings.hide_self {
- if let Some(curr) = current_user {
- if u.username == curr.username {
- return false;
- }
- }
- }
- // Username Whitelist
- if self.filter_settings.username_whitelist_enabled {
- if !self.filter_settings.username_whitelist.contains(&u.username) {
- return false;
- }
- }
- // Username Blacklist
- if self.filter_settings.username_blacklist_enabled {
- if self.filter_settings.username_blacklist.contains(&u.username) {
- return false;
- }
- }
- // Role Filtering
- if self.filter_settings.role_whitelist_enabled || self.filter_settings.role_blacklist_enabled {
- if let Some(role_name) = self.roles.get(&u.role_id) {
- if self.filter_settings.role_whitelist_enabled {
- if !self.filter_settings.role_whitelist.contains(role_name) {
- return false;
- }
- }
- if self.filter_settings.role_blacklist_enabled {
- if self.filter_settings.role_blacklist.contains(role_name) {
- return false;
- }
- }
- }
- }
- true
- });
- self.users = users;
- self.error_message = None;
- }
- Err(e) => {
- self.error_message = Some(format!("Failed to parse users: {}", e));
- }
- }
- } else {
- self.users.clear();
- }
- } else {
- self.error_message = Some(format!("Failed to fetch users: {}", response.error.unwrap_or_default()));
- }
- }
- Err(e) => {
- self.error_message = Some(format!("Network error: {}", e));
- }
- }
- self.is_loading = false;
- }
- pub fn show(&mut self, ui: &mut egui::Ui, client: &ApiClient) -> LoginResult {
- let mut result = LoginResult::None;
- ui.vertical_centered(|ui| {
- ui.add_space(50.0);
- ui.heading(egui::RichText::new(&self.ui_settings.title).size(32.0));
- ui.add_space(30.0);
- if self.is_rfid_mode {
- result = self.show_rfid_entry(ui, client);
- } else if self.selected_user.is_none() {
- self.show_user_selection(ui);
- } else {
- result = self.show_pin_entry(ui, client);
- }
- });
- result
- }
- fn show_user_selection(&mut self, ui: &mut egui::Ui) {
- ui.label(egui::RichText::new("Select User").size(self.ui_settings.font_size));
- ui.add_space(20.0);
- if self.is_loading {
- ui.spinner();
- return;
- }
- if let Some(error) = &self.error_message {
- ui.label(egui::RichText::new(error).color(egui::Color32::RED));
- ui.add_space(10.0);
- }
- egui::ScrollArea::vertical().show(ui, |ui| {
- ui.vertical_centered(|ui| {
- for user in &self.users {
- let btn = egui::Button::new(
- egui::RichText::new(&user.name).size(self.ui_settings.font_size * 0.8)
- ).min_size(egui::vec2(300.0, self.ui_settings.button_height));
- if ui.add(btn).clicked() {
- self.selected_user = Some(user.clone());
- self.pin_input.clear();
- self.error_message = None;
- }
- ui.add_space(10.0);
- }
- if self.ui_settings.enable_rfid {
- ui.add_space(20.0);
- let btn = egui::Button::new(
- egui::RichText::new("RFID Sign In").size(self.ui_settings.font_size * 0.8)
- ).min_size(egui::vec2(300.0, self.ui_settings.button_height));
-
- if ui.add(btn).clicked() {
- self.is_rfid_mode = true;
- self.rfid_input.clear();
- self.error_message = None;
- }
- }
- });
- });
- }
- fn show_rfid_entry(&mut self, ui: &mut egui::Ui, client: &ApiClient) -> LoginResult {
- ui.label(egui::RichText::new("Scan RFID Tag").size(self.ui_settings.font_size));
- ui.add_space(20.0);
-
- // Invisible input that keeps focus to capture RFID scanner input
- let response = ui.add(egui::TextEdit::singleline(&mut self.rfid_input).password(true).desired_width(0.0));
-
- // Always request focus
- response.request_focus();
- // Check for Enter key (scanner usually sends Enter at the end)
- // Also check if input length > 0 to avoid empty submissions
- if (response.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter))) ||
- (ui.input(|i| i.key_pressed(egui::Key::Enter)) && !self.rfid_input.is_empty()) {
- return self.attempt_rfid_login(client);
- }
-
- ui.add_space(20.0);
- ui.spinner();
- ui.add_space(20.0);
-
- if ui.add_sized(egui::vec2(150.0, self.ui_settings.button_height), egui::Button::new(egui::RichText::new("Cancel").size(self.ui_settings.font_size * 0.8))).clicked() {
- self.is_rfid_mode = false;
- self.rfid_input.clear();
- self.error_message = None;
- }
- if let Some(error) = &self.error_message {
- ui.add_space(10.0);
- ui.label(egui::RichText::new(error).color(egui::Color32::RED));
- }
- LoginResult::None
- }
- fn show_pin_entry(&mut self, ui: &mut egui::Ui, client: &ApiClient) -> LoginResult {
- if let Some(user) = &self.selected_user {
- ui.label(egui::RichText::new(format!("Hello, {}", user.name)).size(self.ui_settings.font_size));
- ui.add_space(20.0);
-
- ui.label(egui::RichText::new("Enter PIN:").size(self.ui_settings.font_size * 0.8));
- let response = ui.add(egui::TextEdit::singleline(&mut self.pin_input).password(true).font(egui::FontId::proportional(self.ui_settings.font_size)));
-
- // Auto-focus PIN input
- if response.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) {
- return self.attempt_login(client);
- }
-
- ui.add_space(20.0);
- // OSK
- if self.ui_settings.enable_osk {
- self.show_osk(ui);
- ui.add_space(20.0);
- }
-
- let result = ui.horizontal(|ui| {
- // Align buttons to bottom/center with larger size
- let btn_size = egui::vec2(150.0, self.ui_settings.button_height);
- let total_width = btn_size.x * 2.0 + 20.0; // 2 buttons + spacing
- let available_width = ui.available_width();
- let margin = (available_width - total_width) / 2.0;
-
- if margin > 0.0 {
- ui.add_space(margin);
- }
-
- if ui.add_sized(btn_size, egui::Button::new(egui::RichText::new("Back").size(self.ui_settings.font_size * 0.8))).clicked() {
- self.selected_user = None;
- self.pin_input.clear();
- self.error_message = None;
- }
-
- ui.add_space(20.0);
- if ui.add_sized(btn_size, egui::Button::new(egui::RichText::new("Login").size(self.ui_settings.font_size * 0.8))).clicked() {
- return self.attempt_login(client);
- }
-
- LoginResult::None
- }).inner;
- if let Some(error) = &self.error_message {
- ui.add_space(10.0);
- ui.label(egui::RichText::new(error).color(egui::Color32::RED));
- }
-
- return result;
- }
- LoginResult::None
- }
- fn show_osk(&mut self, ui: &mut egui::Ui) {
- let keys = [
- ["1", "2", "3"],
- ["4", "5", "6"],
- ["7", "8", "9"],
- ["CLR", "0", "DEL"],
- ];
- let btn_size = egui::vec2(80.0, 80.0); // Large touch targets
- ui.vertical_centered(|ui| {
- for row in keys {
- ui.horizontal(|ui| {
- // Center the row content
- let row_width = btn_size.x * 3.0 + ui.style().spacing.item_spacing.x * 2.0;
- let available_width = ui.available_width();
- let margin = (available_width - row_width) / 2.0;
- if margin > 0.0 {
- ui.add_space(margin);
- }
- for key in row {
- if ui.add_sized(btn_size, egui::Button::new(egui::RichText::new(key).size(32.0))).clicked() {
- match key {
- "CLR" => self.pin_input.clear(),
- "DEL" => { self.pin_input.pop(); },
- _ => self.pin_input.push_str(key),
- }
- }
- }
- });
- ui.add_space(10.0);
- }
- });
- }
- fn attempt_login(&mut self, client: &ApiClient) -> LoginResult {
- if let Some(user) = &self.selected_user {
- match client.login_pin(&user.username, &self.pin_input) {
- Ok(response) => {
- if response.success {
- self.error_message = None;
- if let (Some(user), Some(token)) = (response.user, response.token) {
- return LoginResult::Success(user, token);
- } else {
- self.error_message = Some("Login successful but no user data or token returned".to_string());
- }
- } else {
- self.error_message = Some(response.error.unwrap_or_else(|| "Login failed".to_string()));
- }
- }
- Err(e) => {
- self.error_message = Some(format!("Login error: {}", e));
- }
- }
- }
- LoginResult::None
- }
- fn attempt_rfid_login(&mut self, client: &ApiClient) -> LoginResult {
- match client.login_token(&self.rfid_input) {
- Ok(response) => {
- if response.success {
- self.error_message = None;
- self.is_rfid_mode = false;
- self.rfid_input.clear();
- if let (Some(user), Some(token)) = (response.user, response.token) {
- return LoginResult::Success(user, token);
- } else {
- self.error_message = Some("Login successful but no user data or token returned".to_string());
- }
- } else {
- self.error_message = Some(response.error.unwrap_or_else(|| "Login failed".to_string()));
- self.rfid_input.clear();
- }
- }
- Err(e) => {
- self.error_message = Some(format!("Login error: {}", e));
- self.rfid_input.clear();
- }
- }
- LoginResult::None
- }
- }
|