diff --git a/package-lock.json b/package-lock.json index 19bf5be..fbf2184 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "@radix-ui/react-progress": "^1.1.0", "@radix-ui/react-select": "^2.1.1", "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-tooltip": "^1.1.2", "@tauri-apps/api": "^2.0.0-beta.15", "@tauri-apps/plugin-fs": "^2.0.0-rc.2", @@ -1251,6 +1252,125 @@ } } }, + "node_modules/@radix-ui/react-toast": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.2.tgz", + "integrity": "sha512-Z6pqSzmAP/bFJoqMAston4eSNa+ud44NSZTiZUmUen+IOZ5nBY8kzuU5WDBVyFXPtcW6yUalOHsxM/BP6Sv8ww==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-collection": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.1", + "@radix-ui/react-portal": "1.1.2", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0", + "@radix-ui/react-visually-hidden": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-context": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", + "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz", + "integrity": "sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-portal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.2.tgz", + "integrity": "sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toast/node_modules/@radix-ui/react-presence": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.1.tgz", + "integrity": "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-tooltip": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.2.tgz", diff --git a/package.json b/package.json index b8d62bc..1fdd091 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pontuall", - "version": "0.1.0", + "version": "0.1.1", "private": true, "scripts": { "dev": "next dev", @@ -22,6 +22,7 @@ "@radix-ui/react-progress": "^1.1.0", "@radix-ui/react-select": "^2.1.1", "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-tooltip": "^1.1.2", "@tauri-apps/api": "^2.0.0-beta.15", "@tauri-apps/plugin-fs": "^2.0.0-rc.2", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index bbfe08b..abc7c06 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -2934,7 +2934,7 @@ dependencies = [ [[package]] name = "pontuall" -version = "0.1.0" +version = "0.1.1" dependencies = [ "argon2", "base64 0.22.1", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 42633fd..8ec34f3 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pontuall" -version = "0.1.0" +version = "0.1.1" description = "PontuAll" authors = ["TockaNest @ https://github.com/tockanest"] edition = "2021" diff --git a/src-tauri/capabilities/main.json b/src-tauri/capabilities/main.json index f13a1a4..c7d71a9 100644 --- a/src-tauri/capabilities/main.json +++ b/src-tauri/capabilities/main.json @@ -1,18 +1,18 @@ { - "$schema": "../gen/schemas/desktop-schema.json", - "identifier": "main-capability", - "description": "Capability for the main window", - "windows": [ - "main" - ], - "permissions": [ - "core:path:default", - "core:event:default", - "core:window:default", - "core:app:default", - "core:image:default", - "core:resources:default", - "core:menu:default", - "core:tray:default" - ] + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "main-capability", + "description": "Capability for the main window", + "windows": [ + "main" + ], + "permissions": [ + "core:path:default", + "core:event:default", + "core:window:default", + "core:app:default", + "core:image:default", + "core:resources:default", + "core:menu:default", + "core:tray:default" + ] } diff --git a/src-tauri/capabilities/splashscreen.json b/src-tauri/capabilities/splashscreen.json index a3d056b..2edfa72 100644 --- a/src-tauri/capabilities/splashscreen.json +++ b/src-tauri/capabilities/splashscreen.json @@ -1,29 +1,29 @@ { - "$schema": "../gen/schemas/desktop-schema.json", - "identifier": "splash-capability", - "description": "Capability for the splashscreen window", - "windows": [ - "splashscreen" - ], - "permissions": [ - "core:window:default", - "core:event:default", - "core:app:default", - "core:image:default", - "core:resources:default", - "core:menu:default", - "core:tray:default", - "fs:default", - "fs:allow-create", - "fs:allow-appconfig-read-recursive", - "fs:allow-appconfig-write-recursive", - { - "identifier": "fs:allow-exists", - "allow": [ - { - "path": "$APPDATA/**" - } - ] - } - ] + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "splash-capability", + "description": "Capability for the splashscreen window", + "windows": [ + "splashscreen" + ], + "permissions": [ + "core:window:default", + "core:event:default", + "core:app:default", + "core:image:default", + "core:resources:default", + "core:menu:default", + "core:tray:default", + "fs:default", + "fs:allow-create", + "fs:allow-appconfig-read-recursive", + "fs:allow-appconfig-write-recursive", + { + "identifier": "fs:allow-exists", + "allow": [ + { + "path": "$APPDATA/**" + } + ] + } + ] } diff --git a/src-tauri/src/acr122u/card/read.rs b/src-tauri/src/acr122u/card/read.rs index ecd1cab..41238dd 100644 --- a/src-tauri/src/acr122u/card/read.rs +++ b/src-tauri/src/acr122u/card/read.rs @@ -1,7 +1,6 @@ use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; -use futures::TryFutureExt; use pcsc::*; use crate::acr122u::card::utils::authenticate::{authenticate_14443_3, KeyType}; @@ -24,7 +23,10 @@ use crate::acr122u::utils::errors::ReaderError; /// block 9 – data block /// block 10 – data block /// block 11 – sector trailer -/// And so on. Be careful when accessing the blocks, as the sector trailer contains the access bits and the key A and key B. Overwriting the sector trailer can make the card unreadable. +/// And so on. +/// Be careful when accessing the blocks, +/// as the sector trailer contains the access bits, +/// and the key A and key B. Overwriting the sector trailer can make the card unreadable. /// /// Reads data from a specified block on a card. /// @@ -49,7 +51,7 @@ use crate::acr122u::utils::errors::ReaderError; /// * The reader name is invalid. /// * The read operation fails. /// * The card type is unsupported. -/// * The operation is cancelled. +/// * The operation is canceled. /// /// # Examples /// @@ -236,7 +238,7 @@ mod tests { .map(|b| format!("{:02X}", b)) .collect::>() .join(" "); - // Convert the hex to readable text + // Convert the hex to a readable text let data_text = data_str .split_whitespace() .map(|s| { diff --git a/src-tauri/src/acr122u/card/write.rs b/src-tauri/src/acr122u/card/write.rs index 146d549..bc1e16a 100644 --- a/src-tauri/src/acr122u/card/write.rs +++ b/src-tauri/src/acr122u/card/write.rs @@ -30,7 +30,7 @@ use crate::acr122u::utils::errors::ReaderError; /// * The data length is not a multiple of the block size. /// * The write operation fails. /// * The card type is unsupported. -/// * The operation is cancelled. +/// * The operation is canceled. /// /// # Examples /// @@ -50,8 +50,7 @@ pub(crate) async fn write_block( cancel_flag: &Arc, ) -> Result { let reader = CString::new(reader) - .map_err(|_| ReaderError::UnsupportedReader("Invalid reader name".to_string())) - .unwrap(); + .map_err(|_| ReaderError::UnsupportedReader("Invalid reader name".to_string()))?; let block_size = block_size.unwrap_or(16); if data.len() < block_size as usize || data.len() % block_size as usize != 0 { @@ -211,17 +210,18 @@ mod tests { use super::*; + #[tokio::test] async fn test_write_single() { let (ctx, reader) = reader().unwrap(); let cancel_flag = Arc::new(AtomicBool::new(false)); let mut buffer = vec![0; 16]; - let data = "Hello, World!"; + let data = "LvKY0TDcb34hbNeJ"; let data_len = data.len(); let copy_len = std::cmp::min(data_len, buffer.len()); buffer[..copy_len].copy_from_slice(&data.as_bytes()[..copy_len]); - let result = write_block(ctx, reader, 4, buffer, Option::from(16), &cancel_flag) + let result = write_block(ctx, reader, 5, buffer, Option::from(16), &cancel_flag) .await .unwrap_or_else(|e| panic!("{:?}", e)); diff --git a/src-tauri/src/acr122u/tauri_commands.rs b/src-tauri/src/acr122u/tauri_commands.rs index 6e24120..292156f 100644 --- a/src-tauri/src/acr122u/tauri_commands.rs +++ b/src-tauri/src/acr122u/tauri_commands.rs @@ -64,10 +64,10 @@ fn connect() -> Result { /// * `true` if the context is valid. /// * `false` if the context is invalid. fn validate_context(context: &Context) -> bool { - return match context.is_valid() { + match context.is_valid() { Ok(_valid) => true, Err(_) => false, - }; + } } /// Reads data from a specified block on the card. @@ -103,7 +103,7 @@ async fn mcp_read(block_number: u16, state: Arc) -> Result, R None, None, ) - .await?; + .await?; Ok(read) } else { Err(ReaderError::PcscError(pcsc::Error::InvalidHandle)) @@ -254,8 +254,8 @@ async fn mcp_write( Option::from(16), &state.cancel_flag, ) - .await - .unwrap_or_else(|e| panic!("{:?}", e)); + .await + .unwrap_or_else(|e| panic!("{:?}", e)); if result { Ok(()) @@ -308,8 +308,8 @@ pub(crate) async fn write_card( /// * `Err(ReaderError)` - If an error occurs during the connection. #[tauri::command] pub(crate) fn get_connection() -> Result { - return match connect() { + match connect() { Ok(result) => Ok(result.reader), Err(e) => Err(e), - }; + } } diff --git a/src-tauri/src/cache/set.rs b/src-tauri/src/cache/set.rs index 0bc85ed..cd8a1ad 100644 --- a/src-tauri/src/cache/set.rs +++ b/src-tauri/src/cache/set.rs @@ -73,12 +73,9 @@ mod tests { use futures::StreamExt; use mongodb::bson::doc; - use mongodb::{bson, Collection}; - use rand::distributions::Alphanumeric; - use rand::prelude::ThreadRng; - use rand::{thread_rng, Rng}; + use mongodb::Collection; + use rand::Rng; use serde_json::json; - use tauri::utils::config::parse::parse_json; use uuid::Uuid; use crate::cache::set::cache_users; @@ -135,7 +132,7 @@ mod tests { for _ in 0..num_entries { // Generate a random day of the current month let random_day = rng.gen_range(1..=31); // Generates a random day between 1 and 31 - // If random_day is not a two-digit number, pad it with a 0 + // If random_day is not a two-digit number, pad it with a 0 let key = format!("{:02}/07/2024", random_day); // Generate a random HourData instance let hour_data = generate_random_hour_data(&mut rng); @@ -149,7 +146,7 @@ mod tests { /// Inserts mock users into the database for testing purposes. #[tokio::test] async fn insert_users() { - let db = create_db_connection("mongodb://localhost:27017") + let db = create_db_connection() .await .unwrap(); let db = db.read().await; @@ -195,7 +192,7 @@ mod tests { /// Tests the `cache_users` function by retrieving users from the database and caching them. #[tokio::test] async fn test_cache_users() { - let db = create_db_connection("mongodb://localhost:27017") + let db = create_db_connection() .await .unwrap(); let db = db.read().await; diff --git a/src-tauri/src/database/connect.rs b/src-tauri/src/database/connect.rs index 7d2b899..f4e34c8 100644 --- a/src-tauri/src/database/connect.rs +++ b/src-tauri/src/database/connect.rs @@ -1,3 +1,5 @@ +use keyring::Entry; +use mongodb::options::{ClientOptions, ServerApi, ServerApiVersion}; use mongodb::{Client, Database}; use std::error::Error; use std::sync::Arc; @@ -5,15 +7,23 @@ use tokio::sync::RwLock; pub(crate) type SharedDatabase = Arc>; -pub(crate) async fn create_db_connection(uri: &str) -> Result> { - let client = match Client::with_uri_str(uri).await { - Ok(client) => client, - Err(e) => { - return Err(Box::new(e)); - } - }; +pub(crate) async fn create_db_connection() -> Result> { + let mongo_db_uri = Entry::new("PontuAll", "mongodb_uri").unwrap(); + let uri = mongo_db_uri.get_password().unwrap_or_else(|_| "PontuAll: Could not find MongoDb at the KeyRing.".to_string()); + println!("URI: {}", uri); + let app_name_get = Entry::new("PontuAll", "app_name").unwrap(); + let app_name = app_name_get.get_password().unwrap_or_else(|_| "PontuAll".to_string()); - let database = client.database("pontuall-dev"); + let mut client_options = + ClientOptions::parse(uri.as_str()).await?; + + // Set the server_api field of the client_options object to set the version of the Stable API on the client + let server_api = ServerApi::builder().version(ServerApiVersion::V1).build(); + client_options.server_api = Some(server_api); + // Get a handle to the cluster + let client = Client::with_options(client_options)?; + + let database = client.database(format!("pontuall_{0}", app_name).as_str()); Ok(Arc::new(RwLock::new(database))) } diff --git a/src-tauri/src/database/tauri_commands.rs b/src-tauri/src/database/tauri_commands.rs index 4f6775b..8036cc9 100644 --- a/src-tauri/src/database/tauri_commands.rs +++ b/src-tauri/src/database/tauri_commands.rs @@ -3,7 +3,7 @@ use hmac::{Hmac, Mac}; use mongodb::bson::doc; use mongodb::Collection; -use base64::{alphabet, engine::{self, general_purpose}, Engine as _}; +use base64::{engine::general_purpose, Engine as _}; use serde_json::{json, Value}; use sha2::Sha256; use std::ops::Deref; diff --git a/src-tauri/src/excel/create.rs b/src-tauri/src/excel/create.rs index 9c6e4db..a2c9f67 100644 --- a/src-tauri/src/excel/create.rs +++ b/src-tauri/src/excel/create.rs @@ -330,7 +330,7 @@ mod tests { /// Tests the `create_excel_relatory` function. #[tokio::test] async fn test_create_excel_relatory() { - let db = create_db_connection("mongodb://localhost:27017") + let db = create_db_connection() .await .unwrap(); get_users_and_cache(db).await; diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index ef3ea15..53c3003 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -17,7 +17,6 @@ use crate::excel::create::create_excel_relatory; use crate::misc::get::version_name; use crate::misc::set_db_uri::insert_uri; use crate::misc::setup::{complete_setup, SetupState}; -use crate::misc::token; use crate::misc::token::verify; mod acr122u; diff --git a/src-tauri/src/misc/get.rs b/src-tauri/src/misc/get.rs index e135b65..044dbba 100644 --- a/src-tauri/src/misc/get.rs +++ b/src-tauri/src/misc/get.rs @@ -2,6 +2,7 @@ pub(crate) fn version_name(version: String) -> String { match version { version if version == "0.1.0" => "Yttrium".to_string(), + version if version == "0.1.1" => "Reforged Yttrium".to_string(), _ => "noname".to_string(), } } diff --git a/src-tauri/src/misc/set_db_uri.rs b/src-tauri/src/misc/set_db_uri.rs index 8b4d7ff..263fbec 100644 --- a/src-tauri/src/misc/set_db_uri.rs +++ b/src-tauri/src/misc/set_db_uri.rs @@ -1,13 +1,22 @@ use keyring::Entry; #[tauri::command] -pub(crate) fn insert_uri(uri: String) -> Result<(), String> { +pub(crate) fn insert_uri(app_name: String, uri: String) -> Result<(), String> { + println!("URI: {}\nApp Name: {}", uri, app_name); let entry = Entry::new("PontuAll", "mongodb_uri") .unwrap_or_else(|e| panic!("Error: {}", e)); // Insert the uri into the keyring entry.set_password(uri.as_str()) .unwrap_or_else(|e| panic!("Error: {}", e)); + + let entry = Entry::new("PontuAll", "app_name") + .unwrap_or_else(|e| panic!("Error: {}", e)); + // Insert the uri into the keyring + entry.set_password(app_name.as_str()) + .unwrap_or_else(|e| panic!("Error: {}", e)); + + Ok(()) } @@ -20,6 +29,6 @@ mod tests { #[test] fn test_insert_uri() { let uri = "mongodb://localhost:27017".to_string(); - assert_eq!(insert_uri(uri), Ok(())); + assert_eq!(insert_uri(uri, "test".to_string()), Ok(())); } } \ No newline at end of file diff --git a/src-tauri/src/misc/setup.rs b/src-tauri/src/misc/setup.rs index 5030c4a..acde739 100644 --- a/src-tauri/src/misc/setup.rs +++ b/src-tauri/src/misc/setup.rs @@ -1,4 +1,3 @@ -use keyring::Entry; use std::sync::Mutex; use tauri::{AppHandle, Emitter, Manager, State, WebviewUrl, WebviewWindowBuilder}; use tokio::task; @@ -13,12 +12,7 @@ pub(crate) struct SetupState { } async fn setup(app: AppHandle) -> Result<(), ()> { - let mongo_db_uri = Entry::new("PontuAll", "mongodb_uri").unwrap(); - let uri = mongo_db_uri.get_password().unwrap_or_else(|_| "PontuAll: Could not find MongoDb at the KeyRing.".to_string()); - - let db_connection = create_db_connection( - uri.as_str() - ).await.unwrap(); + let db_connection = create_db_connection().await.unwrap(); app.manage(db_connection.clone()); let splash_window = app.get_webview_window("splashscreen").unwrap(); diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 9237ea4..bcdaa8a 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,37 +1,37 @@ { - "productName": "PontuAll", - "version": "0.1.0", - "identifier": "PontuAll", - "build": { - "beforeDevCommand": "npm run dev", - "beforeBuildCommand": "npm run build", - "devUrl": "http://localhost:3000", - "frontendDist": "../out" - }, - "app": { - "withGlobalTauri": true, - "windows": [ - { - "label": "splashscreen", - "title": "PontuAll - Inicializando", - "width": 600, - "height": 600, - "resizable": true, - "visible": true, - "url": "/splashscreen", - "center": true, - "closable": false - } - ], - "security": { - "csp": null - } - }, - "bundle": { - "active": true, - "targets": "all", - "icon": [ - "icons/clock.ico" - ] - } + "productName": "PontuAll", + "version": "0.1.1", + "identifier": "PontuAll", + "build": { + "beforeDevCommand": "npm run dev", + "beforeBuildCommand": "npm run build", + "devUrl": "http://localhost:3000", + "frontendDist": "../out" + }, + "app": { + "withGlobalTauri": true, + "windows": [ + { + "label": "splashscreen", + "title": "PontuAll - Inicializando", + "width": 600, + "height": 600, + "resizable": true, + "visible": true, + "url": "/splashscreen", + "center": true, + "closable": false + } + ], + "security": { + "csp": null + } + }, + "bundle": { + "active": true, + "targets": "all", + "icon": [ + "icons/clock.ico" + ] + } } diff --git a/src/assets/tauri.svg b/src/assets/tauri.svg index 31b62c9..38cfcb2 100644 --- a/src/assets/tauri.svg +++ b/src/assets/tauri.svg @@ -1,6 +1,11 @@ - - - - + + + + diff --git a/src/assets/typescript.svg b/src/assets/typescript.svg index 30a5edd..e587787 100644 --- a/src/assets/typescript.svg +++ b/src/assets/typescript.svg @@ -1,13 +1,13 @@ + "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> + width="512.000000pt" height="512.000000pt" viewBox="0 0 512.000000 512.000000" + preserveAspectRatio="xMidYMid meet"> - - + - + diff --git a/src/assets/vite.svg b/src/assets/vite.svg index e7b8dfb..d208b45 100644 --- a/src/assets/vite.svg +++ b/src/assets/vite.svg @@ -1 +1,18 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/components/component/icons.tsx b/src/components/component/icons.tsx index 66feeaa..54dba4c 100644 --- a/src/components/component/icons.tsx +++ b/src/components/component/icons.tsx @@ -1,113 +1,113 @@ import {SVGProps} from "react"; function ChevronRightIcon(props: SVGProps) { - return ( - - - - ) + return ( + + + + ) } function ClockIcon(props: SVGProps) { - return ( - - - - - ) + return ( + + + + + ) } function SettingsIcon(props: SVGProps) { - return ( - - - - - ) + return ( + + + + + ) } function SuccessCircle(props: SVGProps) { - return ( - - - - - - - - - ); + return ( + + + + + + + + + ); } function XIcon(props: SVGProps) { - return ( - - - - - ) + return ( + + + + + ) } function SpinnerIcon(props: SVGProps) { - return ( - - ); + return ( + + ); } export { - ChevronRightIcon, - ClockIcon, - SettingsIcon, - XIcon, - SpinnerIcon, - SuccessCircle + ChevronRightIcon, + ClockIcon, + SettingsIcon, + XIcon, + SpinnerIcon, + SuccessCircle } \ No newline at end of file diff --git a/src/components/component/layout.tsx b/src/components/component/layout.tsx index 5d66f21..0670f9d 100644 --- a/src/components/component/layout.tsx +++ b/src/components/component/layout.tsx @@ -5,28 +5,28 @@ import {cn} from '@/lib/utils' import React from "react"; const fontHeading = Manrope({ - subsets: ['latin'], - display: 'swap', - variable: '--font-heading', + subsets: ['latin'], + display: 'swap', + variable: '--font-heading', }) const fontBody = Manrope({ - subsets: ['latin'], - display: 'swap', - variable: '--font-body', - weight: '400', + subsets: ['latin'], + display: 'swap', + variable: '--font-body', + weight: '400', }) -export default function Layout({children}: {children: React.ReactNode}) { - return ( -
- {children} -
- ) +export default function Layout({children}: { children: React.ReactNode }) { + return ( +
+ {children} +
+ ) } \ No newline at end of file diff --git a/src/components/component/themeprovider.tsx b/src/components/component/themeprovider.tsx index 50b7ed7..5001635 100644 --- a/src/components/component/themeprovider.tsx +++ b/src/components/component/themeprovider.tsx @@ -1,15 +1,15 @@ "use client" export default function ThemeProvider() { - if (typeof window === "undefined") return null - - const theme = localStorage.getItem("theme") - - if (!theme) { - document.documentElement.setAttribute("data-theme", "midnight") - localStorage.setItem("theme", "midnight") - } - - document.documentElement.setAttribute("data-theme", theme as string) - return; + if (typeof window === "undefined") return null + + const theme = localStorage.getItem("theme") + + if (!theme) { + document.documentElement.setAttribute("data-theme", "midnight") + localStorage.setItem("theme", "midnight") + } + + document.documentElement.setAttribute("data-theme", theme as string) + return; } \ No newline at end of file diff --git a/src/components/main/Admin.tsx b/src/components/main/Admin.tsx index 93b945e..3b76ee2 100644 --- a/src/components/main/Admin.tsx +++ b/src/components/main/Admin.tsx @@ -1,74 +1,71 @@ +import TauriApi from "@/lib/Tauri"; import React, {useEffect, useState} from "react"; +import Employees from "@/components/main/subComponents/admin/employees"; +import AddEmployee from "@/components/main/subComponents/admin/addEmployee"; +import {FileText, Search, Settings, Users} from 'lucide-react'; +import {toast} from "@/hooks/use-toast"; +import {checkPermission} from "@/lib/utils"; import {Card, CardContent, CardDescription, CardHeader, CardTitle} from "@/components/ui/card"; import Label from "@/components/ui/label"; import Input from "@/components/ui/input"; -import Employees from "@/components/main/subComponents/admin/employees"; import {Button} from "@/components/ui/button"; import {Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger} from "@/components/ui/dialog"; -import TauriApi from "@/lib/Tauri"; -import AddEmployee from "@/components/main/subComponents/admin/addEmployee"; -import {checkPermission} from "@/lib/utils"; +import {Checkbox} from "@/components/ui/checkbox"; +import {Table, TableBody, TableCell, TableHead, TableHeader, TableRow} from "@/components/ui/table"; +interface AdminProps { + userLogged: UserLogged | Record; + employees: Users; + setUsers: React.Dispatch>; +} +enum Keys { + ClockIn = "ClockIn", + ClockLunchOut = "ClockLunchOut", + ClockLunchReturn = "ClockLunchReturn", + ClockOut = "ClockOut" +} -export default function Admin( - { - userLogged, - employees, - setUsers - }: { - userLogged: UserLogged | {}, - employees: Users, - setUsers: React.Dispatch> - } -) { - - const [permissions, setPermissions] = useState(null) +export default function Admin({userLogged, employees, setUsers}: AdminProps) { + const [permissions, setPermissions] = useState(null); + const [searchTerm, setSearchTerm] = useState(""); + const [selectedEmployee, setSelectedEmployee] = useState(null); + const [selectedDate, setSelectedDate] = useState(""); + const [relStartDate, setRelStartDate] = useState(""); + const [relEndDate, setRelEndDate] = useState(""); + const [showMassChangeDialog, setShowMassChangeDialog] = useState(false); + const [selectedEmployees, setSelectedEmployees] = useState([]); + const [massChangeSearchTerm, setMassChangeSearchTerm] = useState(""); + const [massChangeHourData, setMassChangeHourData] = useState({ + clock_in: "", + lunch_break_out: "", + lunch_break_return: "", + clocked_out: "", + total_hours: "" + }); useEffect(() => { if (!userLogged || Object.keys(userLogged).length === 0) { - window.location.href = "/" + window.location.href = "/"; return; } const user = userLogged as UserLogged; - const ChangeHoursPermissions = [ - "WriteSelf", - "WriteOthers", - "ReadOthers", - "EditHours" + const allPermissions = [ + "WriteSelf", "WriteOthers", "ReadOthers", "EditHours", "CreateReports" ]; - const RelatoryPermissions = [ - "CreateReports" - ]; - - for (const permission of ChangeHoursPermissions) { + allPermissions.forEach(permission => { TauriApi.CheckPermissions(user, [permission as AppPermissions]).then((res) => { - console.log(`Permission ${permission} is ${res}`) - setPermissions((prev) => { - return { - ...prev, - [permission]: res - } - }) - }) - } - }, []) - - const [searchTerm, setSearchTerm] = useState("") - - const [selectedEmployee, setSelectedEmployee] = useState(null) - const [selectedDate, setSelectedDate] = useState("") - - const [relStartDate, setRelStartDate] = useState("") - const [relEndDate, setRelEndDate] = useState("") + setPermissions(prev => ({...prev, [permission]: res})); + }); + }); + }, [userLogged]); const handleSearchChange = (event: React.ChangeEvent) => { setSearchTerm(event.target.value); }; - // Filter employees based on search term const filteredEmployees = searchTerm ? employees.filter(employee => employee.name.toLowerCase().includes(searchTerm.toLowerCase()) || @@ -76,86 +73,212 @@ export default function Admin( ) : employees; + const filteredMassChangeEmployees = massChangeSearchTerm + ? employees.filter(employee => + employee.name.toLowerCase().includes(massChangeSearchTerm.toLowerCase()) || + employee.email?.toLowerCase().includes(massChangeSearchTerm.toLowerCase()) + ) + : employees; + async function GetUserData(employeeId: string) { - const user = employees.find(employee => employee.id === employeeId) - - if (!user) return; - - setSelectedEmployee(user) - return; + const user = employees.find(employee => employee.id === employeeId); + if (user) setSelectedEmployee(user); } + const createReports = checkPermission("CreateReports", permissions!); + const today = new Date().toLocaleDateString("pt-BR"); + + const formatTimeWithSeconds = (time: string) => { + return time ? `${time}:00` : "N/A"; + }; + + const handleMassChange = async () => { + const modifiedKeys = Object.keys(massChangeHourData).filter(key => massChangeHourData[key as keyof HourData] !== ""); + + if (modifiedKeys.length === 0) { + toast({ + title: "Nenhuma alteração", + description: "Nenhum dado foi modificado para atualização.", + variant: "destructive", + }); + return; + } + + const updatePromises = selectedEmployees.map(async (employeeId) => { + const employee = employees.find(emp => emp.id === employeeId); + if (!employee) return false; + + const updatedHourData: HourData = { + clock_in: "N/A", + lunch_break_out: "N/A", + lunch_break_return: "N/A", + clocked_out: "N/A", + total_hours: "N/A" + }; + + Object.keys(updatedHourData).forEach(key => { + if (massChangeHourData[key as keyof HourData]) { + updatedHourData[key as keyof HourData] = formatTimeWithSeconds(massChangeHourData[key as keyof HourData]); + } + }); + + const employeeUpdates = Object.keys(updatedHourData).map(async (key) => { + let keyToUpdate: Keys; + switch (key) { + case "clock_in": + keyToUpdate = Keys.ClockIn; + break; + case "lunch_break_out": + keyToUpdate = Keys.ClockLunchOut; + break; + case "lunch_break_return": + keyToUpdate = Keys.ClockLunchReturn; + break; + case "clocked_out": + keyToUpdate = Keys.ClockOut; + break; + default: + return true; // Skip total_hours + } + + try { + return await TauriApi.UpdateUser(employeeId, today, keyToUpdate, updatedHourData[key as keyof HourData]); + } catch (e) { + console.error(e); + return false; + } + }); + + const results = await Promise.all(employeeUpdates); + return results.every(Boolean); + }); + + const updateResults = await Promise.all(updatePromises); + + if (updateResults.every(Boolean)) { + setUsers(prev => prev.map(user => { + if (selectedEmployees.includes(user.id)) { + return { + ...user, + hour_data: { + ...user.hour_data, + [today]: { + clock_in: formatTimeWithSeconds(massChangeHourData.clock_in), + lunch_break_out: formatTimeWithSeconds(massChangeHourData.lunch_break_out), + lunch_break_return: formatTimeWithSeconds(massChangeHourData.lunch_break_return), + clocked_out: formatTimeWithSeconds(massChangeHourData.clocked_out), + total_hours: "N/A" // Assuming total_hours is calculated elsewhere + } + } + }; + } + return user; + })); + + toast({ + title: "Atualização bem-sucedida", + description: "Os dados foram atualizados com sucesso para todos os funcionários selecionados.", + }); + } else { + toast({ + title: "Erro na atualização", + description: "Ocorreu um erro ao atualizar os dados de alguns funcionários.", + variant: "destructive", + }); + } + + setShowMassChangeDialog(false); + }; + + const toggleAllEmployees = (checked: boolean) => { + if (checked) { + setSelectedEmployees(filteredMassChangeEmployees.map(emp => emp.id)); + } else { + setSelectedEmployees([]); + } + }; return ( -
-

Administração

-

- Administre o sistema aqui. -

-
- +
+
+

Administração

+

+ Gerencie o sistema e os funcionários aqui. +

+
+
+ - Pontos + Pontos Verifique os Pontos de Funcionários -
- - -
-
- +
+
+ + +
+
+ - + - - Gerenciamento - + Gerenciamento Funções de Gerenciamento - -
- - + +
+ + - + - - Gerar Relatório - + Gerar Relatório -
- - setRelStartDate(e.target.value)}/> - - setRelEndDate(e.target.value)}/> +
+
+ + setRelStartDate(e.target.value)} + /> +
+
+ + setRelEndDate(e.target.value)} + /> +
+ }} variant="secondary">Gerar
@@ -178,6 +308,114 @@ export default function Admin(
+ + + + + Selecionar Funcionários para Mudança em Massa + +
+
+ + setMassChangeSearchTerm(e.target.value)} + className="pl-10" + /> +
+
+ + + + + + + Nome + Data + Entrada + Saída Almoço + Retorno Almoço + Saída + + + + {filteredMassChangeEmployees.map((employee) => ( + + + { + setSelectedEmployees(prev => + checked + ? [...prev, employee.id] + : prev.filter(id => id !== employee.id) + ); + }} + aria-label={`Select ${employee.name}`} + /> + + {employee.name} + {today} + {employee.hour_data[today]?.clock_in || "N/A"} + {employee.hour_data[today]?.lunch_break_out || "N/A"} + {employee.hour_data[today]?.lunch_break_return || "N/A"} + {employee.hour_data[today]?.clocked_out || "N/A"} + + ))} + +
+
+
+
+ + setMassChangeHourData(prev => ({...prev, clock_in: e.target.value}))} + className="text-foreground bg-background border-input" + /> +
+
+ + setMassChangeHourData(prev => ({...prev, lunch_break_out: e.target.value}))} + className="text-foreground bg-background border-input" + /> +
+
+ + setMassChangeHourData(prev => ({...prev, lunch_break_return: e.target.value}))} + className="text-foreground bg-background border-input" + /> +
+
+ + setMassChangeHourData(prev => ({...prev, clocked_out: e.target.value}))} + className="text-foreground bg-background border-input" + /> +
+
+
+ +
+
- ) + ); } \ No newline at end of file diff --git a/src/components/main/Home.tsx b/src/components/main/Home.tsx index a66fe44..73a257b 100644 --- a/src/components/main/Home.tsx +++ b/src/components/main/Home.tsx @@ -213,16 +213,16 @@ export default function HomePage( if (currentTime.isBefore(MinLunchTimeDate.subtract(parseInt(MinutosTolerancia), 'minutes'))) { // Show warning if it's too early to clock out for lunch setDialogMessage({ - message: `O ponto de saída/retorno do almoço deve ser batido após as ${MinLunchTimeDate.format("HH:mm:ss")}`, + message: `O ponto de saída/retorno do almoço deve ser batido após as ${MinLunchTimeDate.format("HH:mm:ss")} - T01`, type: "warning", release: "clock_out" }); setMessageDialogOpen(true); return; - } else if (currentTime.isAfter(MinLunchTimeDate)) { + } else if (currentTime.isAfter(moment(`${user.lunch_time}`, "HH:mm").add(parseInt(MinutosTolerancia), 'minutes'))) { // Show warning if it's too late to clock out for lunch setDialogMessage({ - message: `O ponto de saída/retorno do almoço deve ser batido após às ${MinLunchTimeDate.format("HH:mm:ss")}`, + message: `O ponto de saída/retorno do almoço deve ser batido após às ${MinLunchTimeDate.format("HH:mm:ss")} - T02`, type: "warning", release: "clock_out" }); @@ -240,7 +240,15 @@ export default function HomePage( const currentTime = moment(Date.now()); // Calculate the maximum allowed time for lunch return, considering the tolerance - const maxAllowedTimeForReturn = MaxLunchTimeDate.clone().add(parseInt(MinutosTolerancia) + 60, 'minutes'); + const maxAllowedTimeForReturn = MaxLunchTimeDate.clone().add(parseInt(MinutosTolerancia), 'minutes'); + const minAllowedTimeForReturn = MaxLunchTimeDate.clone().subtract(parseInt(MinutosTolerancia), 'minutes'); + console.debug( + `Lunch Start Time: ${lunchStartTime.format("HH:mm:ss")}\n`, + `Max Lunch Time: ${MaxLunchTimeDate.format("HH:mm:ss")}\n`, + `Max Allowed Time: ${maxAllowedTimeForReturn.format("HH:mm:ss")}\n`, + `Min Allowed Time: ${minAllowedTimeForReturn.format("HH:mm:ss")}\n`, + `Current Time: ${currentTime.format("HH:mm:ss")}\n` + ) // Check if the user is trying to clock in after the maximum time if (currentTime.isAfter(maxAllowedTimeForReturn)) { @@ -252,10 +260,11 @@ export default function HomePage( }); setMessageDialogOpen(true); return; - } else if (currentTime.isBefore(lunchStartTime)) { + } else if (currentTime.isBefore(minAllowedTimeForReturn)) { + console.debug(`Hey ${user.name}!\nCalm down big boy, you can't return yet! Go do something else while you wait lmao.`) // Show warning if it's too early to clock in after lunch setDialogMessage({ - message: `O ponto de retorno do almoço deve ser batido após as ${lunchStartTime.format("HH:mm:ss")}, deseja bater o retorno agora?`, + message: `O ponto de retorno do almoço deve ser batido após as ${minAllowedTimeForReturn.format("HH:mm:ss")}, deseja bater o retorno agora?`, type: "bypass-clock-lunch-return", showDefaultCancel: true }); @@ -263,7 +272,7 @@ export default function HomePage( return; } - // Proceed with updating user hour data if it's time or past time to clock in after lunch + // Proceed with updating user hour data if it's time or if the user wants to skip the validation updateUserHourData(user, time, check, "ClockLunchReturn"); return; } @@ -480,7 +489,7 @@ export default function HomePage( } ].map((button, index) => ( button.show && ( - <> +
{ button.release !== "" && button.release === "clock_out" && (
) )) } diff --git a/src/components/main/Settings.tsx b/src/components/main/Settings.tsx index a9facde..8a5b495 100644 --- a/src/components/main/Settings.tsx +++ b/src/components/main/Settings.tsx @@ -5,312 +5,312 @@ import Label from "@/components/ui/label"; import {allTimezones, useTimezoneSelect} from "react-timezone-select" import {Button} from "@/components/ui/button"; import { - Dialog, - DialogClose, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, - DialogTrigger + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger } from "@/components/ui/dialog"; import {SpinnerIcon} from "@/components/component/icons"; import TauriApi from "@/lib/Tauri"; const labelStyle = "original" const timezones = { - ...allTimezones, + ...allTimezones, } type SettingsProps = { - timezone: string - dateFormat: "12" | "24" - hourFormat: "HH:MM" | "HH:MM:SS" - theme: "deepsea" | "midnight" | "pastel" - setTimezone: React.Dispatch> - setDateFormat: React.Dispatch> - setHourFormat: React.Dispatch> - setTheme: React.Dispatch> + timezone: string + dateFormat: "12" | "24" + hourFormat: "HH:MM" | "HH:MM:SS" + theme: "deepsea" | "midnight" | "pastel" + setTimezone: React.Dispatch> + setDateFormat: React.Dispatch> + setHourFormat: React.Dispatch> + setTheme: React.Dispatch> } export default function Settings( - { - theme, - setTheme, - hourFormat, - setHourFormat, - dateFormat, - setDateFormat, - timezone, - setTimezone - }: SettingsProps + { + theme, + setTheme, + hourFormat, + setHourFormat, + dateFormat, + setDateFormat, + timezone, + setTimezone + }: SettingsProps ) { - - const {options, parseTimezone} = useTimezoneSelect({labelStyle, timezones}) - const [cardData, setCardData] = useState<{ block: number, data: string }[]>([]); - const [cardDataError, setCardDataError] = useState(""); - const [reader, setReader] = useState(""); - const [open, setOpen] = useState(false); - - async function HandleReadCard() { - try { - if (typeof window !== "undefined") { - const readableBlocks = [4, 5, 6, 8] - for (const block of readableBlocks) { - const data = await TauriApi.ReadCard(block) - setCardData((prev) => [...prev, {block, data}]) - } - } - } catch (e: any) { - console.log(e) - if (e.error === "Card Error") { - // {error: 'Card Error', card: 'invalid utf-8 sequence of 1 bytes from index 0', message: 'An internal error has been detected, but the source is unknown'} - if (e.message.includes("source is unknown")) { - setCardDataError(`Erro desconhecido ao ler o cartão: **${e.card}**\nEste tipo de erro normalmente ocorre com cartões formatados incorretamente.`) - } - } - } - } - - async function HandleCloseRead() { - setCardData([]) - - try { - if (typeof window !== "undefined") { - await TauriApi.CloseReader() - } - } catch (e: any) { - console.log(e) - } - } - - async function HandleReaderConnectivityTest() { - try { - if (typeof window !== "undefined") { - const reader = await TauriApi.GetReaderConnection() - if (reader !== "") { - setReader(reader) - } - } - } catch (e: any) { - console.log(e) - } - } - - return ( -
-

Configurações

-

- Aqui você pode configurar o app. -

-
- - - Geral - Configurações Gerais do App - - -
- - -
-
- - -
-
- - -
-
- - -
-
-
- - - Leitor NFC - Configure seu Leitor NFC - - -
- - - - - - - - Teste de Conectividade - - - { - reader === "" ? ( - - Não foi possível conectar ao leitor. - - ) : ( - - Conectado ao leitor {reader}. - - ) - } - - - - - - - { - setOpen(false) - HandleCloseRead() - }}> - - - Informações do Cartão - - - { - cardData.length === 0 ? "Aproxime o cartão do leitor." : "Cartão lido com sucesso." - } - - -
- { - cardData.length === 0 && !cardDataError && ( -
- -
- ) - } - {cardData.map((data) => ( -
-

- Bloco: {data.block} -

-

- Dados: { - // Remove null characters - data.data.replace(/\0/g, "") - } -

-
- ) - )} - { - cardDataError && ( -
+ + const {options, parseTimezone} = useTimezoneSelect({labelStyle, timezones}) + const [cardData, setCardData] = useState<{ block: number, data: string }[]>([]); + const [cardDataError, setCardDataError] = useState(""); + const [reader, setReader] = useState(""); + const [open, setOpen] = useState(false); + + async function HandleReadCard() { + try { + if (typeof window !== "undefined") { + const readableBlocks = [4, 5, 6, 8] + for (const block of readableBlocks) { + const data = await TauriApi.ReadCard(block) + setCardData((prev) => [...prev, {block, data}]) + } + } + } catch (e: any) { + console.log(e) + if (e.error === "Card Error") { + // {error: 'Card Error', card: 'invalid utf-8 sequence of 1 bytes from index 0', message: 'An internal error has been detected, but the source is unknown'} + if (e.message.includes("source is unknown")) { + setCardDataError(`Erro desconhecido ao ler o cartão: **${e.card}**\nEste tipo de erro normalmente ocorre com cartões formatados incorretamente.`) + } + } + } + } + + async function HandleCloseRead() { + setCardData([]) + + try { + if (typeof window !== "undefined") { + await TauriApi.CloseReader() + } + } catch (e: any) { + console.log(e) + } + } + + async function HandleReaderConnectivityTest() { + try { + if (typeof window !== "undefined") { + const reader = await TauriApi.GetReaderConnection() + if (reader !== "") { + setReader(reader) + } + } + } catch (e: any) { + console.log(e) + } + } + + return ( +
+

Configurações

+

+ Aqui você pode configurar o app. +

+
+ + + Geral + Configurações Gerais do App + + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + Leitor NFC + Configure seu Leitor NFC + + +
+ + + + + + + + Teste de Conectividade + + + { + reader === "" ? ( + + Não foi possível conectar ao leitor. + + ) : ( + + Conectado ao leitor {reader}. + + ) + } + + + + + + + { + setOpen(false) + HandleCloseRead() + }}> + + + Informações do Cartão + + + { + cardData.length === 0 ? "Aproxime o cartão do leitor." : "Cartão lido com sucesso." + } + + +
+ { + cardData.length === 0 && !cardDataError && ( +
+ +
+ ) + } + {cardData.map((data) => ( +
+

+ Bloco: {data.block} +

+

+ Dados: { + // Remove null characters + data.data.replace(/\0/g, "") + } +

+
+ ) + )} + { + cardDataError && ( +
$1").replace(/\n/g, "
") + __html: cardDataError.replace(/\*\*(.*?)\*\*/g, "$1").replace(/\n/g, "
") }} /> -
- ) - } -
- - - - - -
- -
-
-
-
-
-
- ) +
+ ) + } +
+ + + + + +
+ +
+
+
+
+
+
+ ) } \ No newline at end of file diff --git a/src/components/main/subComponents/admin/addEmployee.tsx b/src/components/main/subComponents/admin/addEmployee.tsx index a607fea..49df1d6 100644 --- a/src/components/main/subComponents/admin/addEmployee.tsx +++ b/src/components/main/subComponents/admin/addEmployee.tsx @@ -22,447 +22,447 @@ import {ChevronDownIcon} from "@radix-ui/react-icons"; import {Checkbox} from "@/components/ui/checkbox"; export default function AddEmployee( - {setUsers}: { setUsers: React.Dispatch> } + {setUsers}: { setUsers: React.Dispatch> } ) { - - const [openNewUserModal, setOpenNewUserModal] = useState(false) - const [cardApproach, setCardApproach] = useState(false) - const [newUser, setNewUser] = useState({ - name: "", - email: "", - role: "", - lunch_time: "", - phone: "" - }) - - const [permissionsDropdown, setPermissionsDropdown] = useState(false) - const [modifiesOthers, setModifiesOthers] = useState(false) - const [selfDelete, setSelfDelete] = useState(false) - const [deletesOthers, setDeletesOthers] = useState(false) - const [editHours, setEditHours] = useState(false) - const [editPermissions, setEditPermissions] = useState(false) - const [createReports, setCreateReports] = useState(false) - const [supervisor, setSupervisor] = useState(false) - const [administrator, setAdministrator] = useState(false) - - const [success, setSuccess] = useState(false) - - async function HandleReadCard() { - try { - if (typeof window !== "undefined") { - // This is following the standard I set for my old company's Mifare 1k Classic cards. - // Modify this to fit your needs. - // If you're not using Mifare 1k Classic cards, you'll need to modify the rust backend to read your card. - const blocks = { - "name": 5, - "surname": 6, - "phone": 8, - } - - let name = ""; - let surname = ""; - let phone = ""; - - // Read card using blocks - for (const [key, value] of Object.entries(blocks)) { - const data = await TauriApi.ReadCard(value) - switch (key) { - case "name": - // Replace all null bytes, spaces and new lines - const nameData = data.replaceAll(/\0/g, "").replaceAll(/\s/g, "").replaceAll(/\n/g, "") - // Trim the data to 16 bytes max - name = nameData.substring(0, 16) - case "surname": - const surnameData = data.replaceAll(/\0/g, "").replaceAll(/\n/g, "") - surname = surnameData.substring(0, 16) - break; - case "phone": - const phoneData = data.replaceAll(/\0/g, "").replaceAll(/\s/g, "").replaceAll(/\n/g, "") - phone = phoneData.substring(0, 16) - break; - } - } - // Cut the name and surname to 16 bytes max. - // Sorry, most of the time you'll need to adjust this to fit your needs. - // Mifare Classic cards only support 16 bytes per block. - const nameData = (name + " " + surname).substring(0, 16) - - setNewUser({ - name: nameData, - email: "", - role: "", - lunch_time: "", - phone: phone - }) - - } - } catch (e: any) { - console.log(e) - } - } - - async function HandleCloseRead() { - try { - if (typeof window !== "undefined") { - await TauriApi.CloseReader() - } - } catch (e: any) { - console.log(e) - } - } - - async function HandleAddEmployee( - name: string, - email: string, - role: string, - lunch_time: string, - phone: string, - permissions = { - modifiesOthers, - selfDelete, - deletesOthers, - editHours, - editPermissions, - createReports, - supervisor, - administrator - } - ) { - try { - if (typeof window !== "undefined") { - const id = await TauriApi.GenerateUserId() - // await TauriApi.WriteCard(5, id) - - const set_permissions = [ - { - permission: "ReadSelf", - value: true - }, - { - permission: "ReadOthers", - value: true - }, - { - permission: "WriteSelf", - value: true - }, - { - permission: "WriteOthers", - value: permissions.modifiesOthers - }, - { - permission: "DeleteSelf", - value: permissions.selfDelete - }, - { - permission: "DeleteOthers", - value: permissions.deletesOthers - }, - { - permission: "EditHours", - value: permissions.editHours - }, - { - permission: "EditHierarchy", - value: permissions.editPermissions - }, - { - permission: "CreateReports", - value: permissions.createReports - }, - { - permission: "Supervisor", - value: permissions.supervisor - }, - { - permission: "Administrator", - value: permissions.administrator - } - ] - // Get what permissions are enabled and join their names into a single string - const permissions_str = set_permissions - .filter((permission) => permission.value) - .map((permission) => permission.permission) - .join(",") - - const newUser = await TauriApi.InsertNewUser( - id, - name, - email, - role, - lunch_time, - phone, - permissions_str - ) - - if (newUser) { - await TauriApi.GetCache().then((data) => { - const users = Object.entries(data).map(([key, value]) => { - return { - ...value - } - }) as Users - setUsers(users) - }) - - setCardApproach(true) - const write = await TauriApi.WriteCard(5, id) - if (write) { - setCardApproach(false) - setSuccess(true) - } - } - - - } - } catch (e: any) { - console.log(e) - } - } - - useEffect(() => { - // Set all other permissions to false - if (administrator) { - const permissions = [ - setModifiesOthers, - setSelfDelete, - setDeletesOthers, - setEditHours, - setEditPermissions, - setCreateReports, - setSupervisor - ] - - permissions.forEach((permission) => permission(false)) - } else if (supervisor) { - const permissions = [ - setModifiesOthers, - setSelfDelete, - setDeletesOthers, - setEditHours, - setEditPermissions, - setCreateReports, - setAdministrator - ] - - permissions.forEach((permission) => permission(false)) - } - }, [administrator, supervisor]) - - return ( - - - - - - - - Adicionar Funcionário - - - Insira ou aproxime o cartão para importar os dados. - - -
- {cardApproach && ( - <> -
- Aproxime o cartão do leitor. - -
- - )} - - { - if (e.target.value.length > 24) return; - setNewUser({...newUser, name: e.target.value}) - }} - /> - - setNewUser({...newUser, email: e.target.value})} - /> - - setNewUser({...newUser, role: e.target.value})} - /> - - setNewUser({...newUser, lunch_time: e.target.value})} - /> - - setNewUser({...newUser, phone: e.target.value})} - /> - - setPermissionsDropdown(!permissionsDropdown)} - > - - - - event.preventDefault()} - > - { - [ - { - name: "Modifica Outros", - state: modifiesOthers, - setState: setModifiesOthers - }, - { - name: "Deleta Próprio", - state: selfDelete, - setState: setSelfDelete - }, - { - name: "Deleta Outros", - state: deletesOthers, - setState: setDeletesOthers - }, - { - name: "Edita Horas", - state: editHours, - setState: setEditHours - }, - { - name: "Edita Permissões", - state: editPermissions, - setState: setEditPermissions - }, - { - name: "Cria Relatórios", - state: createReports, - setState: setCreateReports - }, - { - name: "Supervisor", - state: supervisor, - setState: setSupervisor - }, - { - name: "Administrador", - state: administrator, - setState: setAdministrator - } - ].map((permission) => ( - event.preventDefault()} - disabled= - { - administrator && permission.name !== "Administrador" || - supervisor && permission.name !== "Supervisor" - } - > - permission.setState(!permission.state)} - /> - {permission.name} - - )) - } - - -
- { - success && ( -
- -

Funcionário Adicionado com Sucesso!

-
- ) - } -
- - - -
-
-
- ) + + const [openNewUserModal, setOpenNewUserModal] = useState(false) + const [cardApproach, setCardApproach] = useState(false) + const [newUser, setNewUser] = useState({ + name: "", + email: "", + role: "", + lunch_time: "", + phone: "" + }) + + const [permissionsDropdown, setPermissionsDropdown] = useState(false) + const [modifiesOthers, setModifiesOthers] = useState(false) + const [selfDelete, setSelfDelete] = useState(false) + const [deletesOthers, setDeletesOthers] = useState(false) + const [editHours, setEditHours] = useState(false) + const [editPermissions, setEditPermissions] = useState(false) + const [createReports, setCreateReports] = useState(false) + const [supervisor, setSupervisor] = useState(false) + const [administrator, setAdministrator] = useState(false) + + const [success, setSuccess] = useState(false) + + async function HandleReadCard() { + try { + if (typeof window !== "undefined") { + // This is following the standard I set for my old company's Mifare 1k Classic cards. + // Modify this to fit your needs. + // If you're not using Mifare 1k Classic cards, you'll need to modify the rust backend to read your card. + const blocks = { + "name": 5, + "surname": 6, + "phone": 8, + } + + let name = ""; + let surname = ""; + let phone = ""; + + // Read card using blocks + for (const [key, value] of Object.entries(blocks)) { + const data = await TauriApi.ReadCard(value) + switch (key) { + case "name": + // Replace all null bytes, spaces and new lines + const nameData = data.replaceAll(/\0/g, "").replaceAll(/\s/g, "").replaceAll(/\n/g, "") + // Trim the data to 16 bytes max + name = nameData.substring(0, 16) + case "surname": + const surnameData = data.replaceAll(/\0/g, "").replaceAll(/\n/g, "") + surname = surnameData.substring(0, 16) + break; + case "phone": + const phoneData = data.replaceAll(/\0/g, "").replaceAll(/\s/g, "").replaceAll(/\n/g, "") + phone = phoneData.substring(0, 16) + break; + } + } + // Cut the name and surname to 16 bytes max. + // Sorry, most of the time you'll need to adjust this to fit your needs. + // Mifare Classic cards only support 16 bytes per block. + const nameData = (name + " " + surname).substring(0, 16) + + setNewUser({ + name: nameData, + email: "", + role: "", + lunch_time: "", + phone: phone + }) + + } + } catch (e: any) { + console.log(e) + } + } + + async function HandleCloseRead() { + try { + if (typeof window !== "undefined") { + await TauriApi.CloseReader() + } + } catch (e: any) { + console.log(e) + } + } + + async function HandleAddEmployee( + name: string, + email: string, + role: string, + lunch_time: string, + phone: string, + permissions = { + modifiesOthers, + selfDelete, + deletesOthers, + editHours, + editPermissions, + createReports, + supervisor, + administrator + } + ) { + try { + if (typeof window !== "undefined") { + const id = await TauriApi.GenerateUserId() + // await TauriApi.WriteCard(5, id) + + const set_permissions = [ + { + permission: "ReadSelf", + value: true + }, + { + permission: "ReadOthers", + value: true + }, + { + permission: "WriteSelf", + value: true + }, + { + permission: "WriteOthers", + value: permissions.modifiesOthers + }, + { + permission: "DeleteSelf", + value: permissions.selfDelete + }, + { + permission: "DeleteOthers", + value: permissions.deletesOthers + }, + { + permission: "EditHours", + value: permissions.editHours + }, + { + permission: "EditHierarchy", + value: permissions.editPermissions + }, + { + permission: "CreateReports", + value: permissions.createReports + }, + { + permission: "Supervisor", + value: permissions.supervisor + }, + { + permission: "Administrator", + value: permissions.administrator + } + ] + // Get what permissions are enabled and join their names into a single string + const permissions_str = set_permissions + .filter((permission) => permission.value) + .map((permission) => permission.permission) + .join(",") + + const newUser = await TauriApi.InsertNewUser( + id, + name, + email, + role, + lunch_time, + phone, + permissions_str + ) + + if (newUser) { + await TauriApi.GetCache().then((data) => { + const users = Object.entries(data).map(([key, value]) => { + return { + ...value + } + }) as Users + setUsers(users) + }) + + setCardApproach(true) + const write = await TauriApi.WriteCard(5, id) + if (write) { + setCardApproach(false) + setSuccess(true) + } + } + + + } + } catch (e: any) { + console.log(e) + } + } + + useEffect(() => { + // Set all other permissions to false + if (administrator) { + const permissions = [ + setModifiesOthers, + setSelfDelete, + setDeletesOthers, + setEditHours, + setEditPermissions, + setCreateReports, + setSupervisor + ] + + permissions.forEach((permission) => permission(false)) + } else if (supervisor) { + const permissions = [ + setModifiesOthers, + setSelfDelete, + setDeletesOthers, + setEditHours, + setEditPermissions, + setCreateReports, + setAdministrator + ] + + permissions.forEach((permission) => permission(false)) + } + }, [administrator, supervisor]) + + return ( + + + + + + + + Adicionar Funcionário + + + Insira ou aproxime o cartão para importar os dados. + + +
+ {cardApproach && ( + <> +
+ Aproxime o cartão do leitor. + +
+ + )} + + { + if (e.target.value.length > 24) return; + setNewUser({...newUser, name: e.target.value}) + }} + /> + + setNewUser({...newUser, email: e.target.value})} + /> + + setNewUser({...newUser, role: e.target.value})} + /> + + setNewUser({...newUser, lunch_time: e.target.value})} + /> + + setNewUser({...newUser, phone: e.target.value})} + /> + + setPermissionsDropdown(!permissionsDropdown)} + > + + + + event.preventDefault()} + > + { + [ + { + name: "Modifica Outros", + state: modifiesOthers, + setState: setModifiesOthers + }, + { + name: "Deleta Próprio", + state: selfDelete, + setState: setSelfDelete + }, + { + name: "Deleta Outros", + state: deletesOthers, + setState: setDeletesOthers + }, + { + name: "Edita Horas", + state: editHours, + setState: setEditHours + }, + { + name: "Edita Permissões", + state: editPermissions, + setState: setEditPermissions + }, + { + name: "Cria Relatórios", + state: createReports, + setState: setCreateReports + }, + { + name: "Supervisor", + state: supervisor, + setState: setSupervisor + }, + { + name: "Administrador", + state: administrator, + setState: setAdministrator + } + ].map((permission) => ( + event.preventDefault()} + disabled= + { + administrator && permission.name !== "Administrador" || + supervisor && permission.name !== "Supervisor" + } + > + permission.setState(!permission.state)} + /> + {permission.name} + + )) + } + + +
+ { + success && ( +
+ +

Funcionário Adicionado com Sucesso!

+
+ ) + } +
+ + + +
+
+
+ ) } \ No newline at end of file diff --git a/src/components/splashscreen/FirstUser.tsx b/src/components/splashscreen/FirstUser.tsx new file mode 100644 index 0000000..a2f1e13 --- /dev/null +++ b/src/components/splashscreen/FirstUser.tsx @@ -0,0 +1,151 @@ +import Label from "@/components/ui/label"; +import {Tooltip, TooltipContent, TooltipProvider, TooltipTrigger} from "@/components/ui/tooltip"; +import Input from "@/components/ui/input"; +import {Button} from "@/components/ui/button"; +import React, {useState} from "react"; +import {Check, X} from "lucide-react"; + +type FirstUserProps = { + firstUser: FirstUser, + setFirstUser: React.Dispatch>, + setSetupStep: React.Dispatch> +} + +interface PasswordRequirement { + regex: RegExp + message: string +} + +export default function FirstUser( + { + firstUser, + setFirstUser, + setSetupStep + }: FirstUserProps +) { + + const [showTooltip, setShowTooltip] = useState(false) + + const passwordRequirements: PasswordRequirement[] = [ + {regex: /.{10,}/, message: "At least 10 characters long"}, + {regex: /[A-Z]/, message: "At least one uppercase letter"}, + {regex: /[a-z]/, message: "At least one lowercase letter"}, + {regex: /[0-9]/, message: "At least one number"}, + {regex: /[^A-Za-z0-9]/, message: "At least one special character"}, + ] + + // Fucking hate Typescript sometimes. + function checkEmptyKeys(): boolean { + return Object.keys(firstUser).some(key => { + //@ts-expect-error + if (typeof firstUser[key] === "object" && firstUser[key] !== null) { + //@ts-expect-error + return Object.keys(firstUser[key]).some(k => { + //@ts-expect-error + return firstUser[key][k] === "" + }) + } + //@ts-expect-error + return firstUser[key] === "" + }) + } + + const handlePasswordChange = (e: React.ChangeEvent) => { + setFirstUser({ + ...firstUser, + password: e.target.value + }) + } + + return ( +
+
+

+ Usuário Administrador +

+

+ O primeiro usuário é o administrador do sistema, podendo configurar e gerenciar o aplicativo com total + liberdade. +

+
+
+
+ + setShowTooltip(true)} + onBlur={() => setShowTooltip(false)} + className="pr-10" + /> + +
+
+ + + + +
+ setShowTooltip(true)} + onBlur={() => setShowTooltip(false)} + className="pr-10" + /> + {firstUser.password && ( + + {passwordRequirements.every(req => req.regex.test(firstUser.password)) ? ( + + ) : ( + + )} + + )} +
+
+ +
+

Password must have:

+
    + {passwordRequirements.map((req, index) => ( +
  • + {req.regex.test(firstUser.password) ? ( + + ) : ( + + )} + {req.message} +
  • + ))} +
+
+
+
+
+
+
+ + +
+
+
+ ) +} \ No newline at end of file diff --git a/src/components/splashscreen/MongoDbSetup.tsx b/src/components/splashscreen/MongoDbSetup.tsx index 2eba921..2502472 100644 --- a/src/components/splashscreen/MongoDbSetup.tsx +++ b/src/components/splashscreen/MongoDbSetup.tsx @@ -5,15 +5,19 @@ import {Button} from "@/components/ui/button"; import React from "react"; type MongoDbSetupProps = { + appName: string, + setAppName: React.Dispatch>, mongoDbUri: string, - setMongoDbUri: (value: string) => void, - setSetupStep: (value: number) => void, + setMongoDbUri: React.Dispatch>, + setSetupStep: React.Dispatch>, setBackendSetup: (value: boolean) => void - Setup: (setBackendSetup: (value: boolean) => void, mongoDbUri?: string) => void + Setup: (setBackendSetup: (value: boolean) => void, appName: string, mongoDbUri: string) => void } export default function MongoDbSetup( { + appName, + setAppName, mongoDbUri, setMongoDbUri, setSetupStep, @@ -23,50 +27,81 @@ export default function MongoDbSetup( ) { return (
-
-

- Configuração do Banco de Dados -

-

- Por favor, responda às seguintes informações para configurar o - banco de dados. -

-
-
-
- - - - - setMongoDbUri(e.target.value)} - /> - - - A URI do MongoDB é o endereço de conexão com o banco de - dados MongoDB. - - - - -
-
- - -

- * Caso esteja utilizando um banco de dados local, pule esta - etapa. +

+

+ Configuração do Banco de Dados +

+

+ Por favor, responda às seguintes informações para configurar o + banco de dados.

+
+
+ + + + + setAppName(e.target.value)} + /> + + + + O nome da aplicação é o nome da sua empresa ou do + projeto. Preferencialmente, utilize um nome curto e + sem espaços. +
+ + Exemplo: pontuall + + +
+
+
+ + + + + setMongoDbUri(e.target.value)} + /> + + + + A URI do MongoDB é o endereço de conexão com o banco de dados MongoDB. +
+ + Exemplo: mongodb://localhost:27017/ + + +
+
+
+
+
+ + +

+ * Caso esteja utilizando um banco de dados local, pule esta + etapa. +

+
+
-
) } \ No newline at end of file diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx index 07c3af4..10a3d9a 100644 --- a/src/components/ui/avatar.tsx +++ b/src/components/ui/avatar.tsx @@ -7,44 +7,44 @@ import * as AvatarPrimitive from "@radix-ui/react-avatar" import {cn} from "@/lib/utils" const Avatar = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({className, ...props}, ref) => ( - + )) Avatar.displayName = AvatarPrimitive.Root.displayName const AvatarImage = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({className, ...props}, ref) => ( - + )) AvatarImage.displayName = AvatarPrimitive.Image.displayName const AvatarFallback = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef + React.ElementRef, + React.ComponentPropsWithoutRef >(({className, ...props}, ref) => ( - + )) AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index a700f87..bf050de 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -8,70 +8,70 @@ import {cn} from "@/lib/utils" type ButtonVariants = VariantProps; const buttonVariants = cva( - "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", - { - variants: { - variant: { - default: "bg-primary text-primary-foreground hover:bg-primary/90", - destructive: - "bg-destructive text-destructive-foreground hover:bg-destructive/90", - outline: - "border border-input bg-background hover:bg-accent hover:text-accent-foreground", - secondary: - "bg-secondary text-secondary-foreground hover:bg-secondary/80", - ghost: "hover:bg-accent hover:text-accent-foreground", - link: "text-primary underline-offset-4 hover:underline", - warning: "bg-warning text-warning-foreground hover:bg-warning/90", - }, - size: { - default: "h-10 px-4 py-2", - sm: "h-9 rounded-md px-3", - lg: "h-11 rounded-md px-8", - icon: "h-10 w-10", - }, - } as ButtonVariants, - defaultVariants: { - variant: "default", - size: "default", - }, - } + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + warning: "bg-warning text-warning-foreground hover:bg-warning/90", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + } as ButtonVariants, + defaultVariants: { + variant: "default", + size: "default", + }, + } ) export interface ButtonProps - extends React.ButtonHTMLAttributes, - VariantProps { - asChild?: boolean - variant?: keyof ButtonVariants["variant"] - size?: keyof ButtonVariants["size"] + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean + variant?: keyof ButtonVariants["variant"] + size?: keyof ButtonVariants["size"] } const Button = React.forwardRef( - ({className, variant, size, disabled, asChild = false, ...props}, ref) => { - return ( - <> - { - asChild ? ( - -