diff --git a/scripts/task_view_to_pdf.py b/scripts/task_view_to_pdf.py
new file mode 100644
index 0000000..762ed5b
--- /dev/null
+++ b/scripts/task_view_to_pdf.py
@@ -0,0 +1,28 @@
+import json
+import sys
+import tkinter as tk
+from tkinter import filedialog
+from weasyprint import HTML, CSS
+
+def create_detailed_pdf(file_path, pdf_html, pdf_css):
+ html = HTML(string=pdf_html)
+ css = CSS(string=pdf_css)
+ html.write_pdf(file_path, stylesheets=[css])
+
+# Create the main window (it will not be displayed)
+root = tk.Tk()
+root.withdraw() # Hide the main window
+
+# Open the file dialog to choose the save location and filename
+file_path = filedialog.asksaveasfilename(
+ defaultextension=".pdf",
+ filetypes=[("PDF files", "*.pdf")],
+ title="Choose location to save the PDF"
+)
+
+# Check if the user provided a file path
+if file_path:
+ pdf_html = sys.argv[1]
+ pdf_css = sys.argv[2]
+
+ create_detailed_pdf(file_path, pdf_html, pdf_css)
diff --git a/src-tauri/src/db/ops.rs b/src-tauri/src/db/ops.rs
index 33864b1..044eb32 100644
--- a/src-tauri/src/db/ops.rs
+++ b/src-tauri/src/db/ops.rs
@@ -4,6 +4,7 @@ use std::sync::Mutex;
const ROOT_GROUP: &str = "/";
const TASK: &str = "Task";
const TASK_GROUP: &str = "TaskGroup";
+const TODAY: &str = "today";
// Singleton because we need a single point of access to the db across the app.
// Mutex because only one process should be able to use the singleton at a time,
@@ -20,7 +21,7 @@ use rusqlite::{params, Connection, Result};
use serde::Serialize;
use std::{collections::HashMap, path::PathBuf};
-#[derive(Serialize, Debug, Clone, Copy)]
+#[derive(Serialize, Debug, Clone, Copy, PartialEq, Eq)]
#[serde(tag = "type")]
pub enum Type {
Task,
@@ -365,12 +366,14 @@ impl Db {
}
pub mod crud_commands {
+ use core::panic;
use std::sync::MutexGuard;
use rusqlite::{params, Connection, Result};
use serde::{Deserialize, Serialize};
- use super::{FetchBasis, Type, DB_SINGLETON, TASK, TASK_GROUP};
+
+ use super::{FetchBasis, Type, DB_SINGLETON, TASK, TASK_GROUP, TODAY};
#[tauri::command]
/// C(R)UD - Reads the database and sends appropriate structure to the frontend.
@@ -575,4 +578,46 @@ pub mod crud_commands {
),
}
}
+
+ /// C(R)UD - Reads database and gets all tasks' names and their parent_group_ids.
+ pub fn get_all_tasks(fetch_basis: FetchBasis) -> Vec<(u64, String)> {
+ let db = DB_SINGLETON.lock().unwrap();
+ let mut all_tasks: Vec<(u64, String)> = Vec::new();
+
+ match db.fetch_records(TODAY, fetch_basis) {
+ Ok(records) => {
+ all_tasks.extend(
+ records
+ .into_iter()
+ .filter(|(_, _, record_type, _, _)| *record_type != Type::TaskGroup)
+ .map(|(_, name, _, _, parent_id)| {
+ if let Some(id) = parent_id {
+ (id, name)
+ } else {
+ panic!("[ERROR] While fetching task names, the task: [{name}] had no parent.");
+ }
+ }),
+ );
+ }
+ Err(err) => panic!("{err}"),
+ }
+
+ all_tasks
+ }
+
+ pub fn get_item(id: u64) -> String {
+ let db = DB_SINGLETON.lock().unwrap();
+ let mut name = String::new();
+
+ if let Some(conn) = &db.db_conn {
+ let res: Option");
+
+ let checked = match is_completed {
+ true => "checked",
+ false => "",
+ };
+
+ for (parent, tasks) in map.into_iter() {
+ match parent.as_str() {
+ "/" => {
+ html.push_str(&format!(
+ "{}",
+ tasks.into_iter()
+ .map(|task| format!(
+ "
");
+ html
+}
+
+#[tauri::command]
+pub fn export_to_pdf() {
+ let active_tasks = get_all_tasks(FetchBasis::Active);
+ let completed_tasks = get_all_tasks(FetchBasis::Completed);
+
+ let active_tasks_inp: Vec<(String, String)> = get_python_input(&active_tasks);
+ let completed_tasks_inp: Vec<(String, String)> = get_python_input(&completed_tasks);
+
+ let pdf_html = format!(
+ "
+
+
+
+
+ {parent}
{}
Active Tasks
+ {}
+
+ Completed Tasks
+ {}
+
+ ",
+ generate_ordered_list(map_parent_to_tasks(active_tasks_inp), false),
+ generate_ordered_list(map_parent_to_tasks(completed_tasks_inp), true)
+ );
+
+ let pdf_css = format!(
+ "
+ @import url(\"https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap\");
+
+ body {{
+ font-family: 'Inter', sans-serif;
+ }}
+
+ h1 {{
+ text-align: center;
+ }}
+
+ ul {{
+ list-style-type: none;
+ }}
+
+ li {{
+ margin-top: 20px;
+ margin-bottom: 20px;
+ }}
+
+ p {{
+ font-weight: bold;
+ }}
+
+ input:checked:after {{
+ color: black;
+ content: '✔';
+ }}
+
+ .sub-task {{
+ display: flex;
+ flex-direction: row;
+ justify-items: space-between;
+ align-items: center;
+ }}
+
+ span {{
+ margin-left: 10px;
+ }}
+
+ .page-break {{
+ page-break-after: always;
+ }}
+ "
+ );
+
+ let output = Command::new("python3")
+ .arg("../scripts/task_view_to_pdf.py")
+ .args([pdf_html, pdf_css])
+ .output()
+ .expect("ok the python script idea didn't work");
+
+ if !output.status.success() {
+ eprintln!("[ERROR]: {}", String::from_utf8_lossy(&output.stderr));
+ }
+}
diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs
index 375c353..d7f5411 100644
--- a/src-tauri/src/lib.rs
+++ b/src-tauri/src/lib.rs
@@ -1,2 +1,3 @@
pub mod db;
-pub mod window;
\ No newline at end of file
+pub mod window;
+pub mod export;
\ No newline at end of file
diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs
index 6f8f04a..80761ad 100644
--- a/src-tauri/src/main.rs
+++ b/src-tauri/src/main.rs
@@ -3,7 +3,7 @@
// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
-use habtrack::{db::{init::DbInitializer, ops}, window};
+use habtrack::{db::{init::DbInitializer, ops}, window, export};
// Start work on database.
// TODO: Once done, merge with main, and pull changes into FEATURE-add-delete-task.
@@ -27,6 +27,7 @@ fn main() {
ops::crud_commands::update_item,
window::open_tomorrow_window,
window::close_tomorrow_window,
+ export::export_to_pdf,
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
diff --git a/src/Constants.jsx b/src/Constants.jsx
index d3d55e2..e0f8f65 100644
--- a/src/Constants.jsx
+++ b/src/Constants.jsx
@@ -25,6 +25,9 @@ const TAURI_UPDATE_ITEM = "update_item";
const TAURI_OPEN_TOMORROW_WINDOW = "open_tomorrow_window";
const TAURI_CLOSE_TOMORROW_WINDOW = "close_tomorrow_window";
+// Export.
+const TAURI_EXPORT_TO_PDF = "export_to_pdf";
+
export {
// Table names
TODAY,
@@ -53,4 +56,7 @@ export {
// New Window,
TAURI_OPEN_TOMORROW_WINDOW,
TAURI_CLOSE_TOMORROW_WINDOW,
+
+ // Export
+ TAURI_EXPORT_TO_PDF,
};
diff --git a/src/views/TasksView/Navbar.jsx b/src/views/TasksView/Navbar.jsx
index fab5cde..affe6ca 100644
--- a/src/views/TasksView/Navbar.jsx
+++ b/src/views/TasksView/Navbar.jsx
@@ -2,12 +2,13 @@ import React from "react";
import PlaylistAddCheckIcon from "@mui/icons-material/PlaylistAddCheck";
import FastForwardIcon from "@mui/icons-material/FastForward";
import AddIcon from "@mui/icons-material/Add";
+import { Download } from "@mui/icons-material";
import { invoke } from "@tauri-apps/api";
import { ROOT, TAURI_OPEN_TOMORROW_WINDOW } from "../../Constants";
// TODO: style this component.
-const Navbar = ({ isSidebarOpen, onAdd, toggleCompleted }) => {
+const Navbar = ({ isSidebarOpen, onExport, onAdd, toggleCompleted }) => {
const seeTomorrow = async () => {
try {
await invoke(TAURI_OPEN_TOMORROW_WINDOW);
@@ -29,6 +30,11 @@ const Navbar = ({ isSidebarOpen, onAdd, toggleCompleted }) => {
Tasks