Compare commits

..

2 Commits

8 changed files with 140 additions and 75 deletions

View File

@ -1,4 +1,5 @@
DB_USER=
DB_NAME=
DB_PASSWORD=
DB_HOST=
DATABASE_HOST=
DATABASE_PORT=
DATABASE_NAME=
DATABASE_USER=
DATABASE_PASSWORD=

View File

@ -1,6 +1,7 @@
import sys
import os
import logging
from typing import Optional
from PySide6.QtWidgets import QMessageBox, QApplication
@ -13,7 +14,7 @@ from ui.window import LibraryWindow
class LibraryApp():
def __init__(self, user_config: UserConfig | None = None):
def __init__(self, user_config: Optional[UserConfig] = None):
setup_logger()
self.logger = logging.getLogger(__name__)
self.logger.info("Starting")
@ -37,7 +38,7 @@ class LibraryApp():
self.window.show()
status = self.qt_app.exec()
self.cleanup()
self.logger.info("Exitting")
self.logger.info("Exiting")
return status
def show_error(self, text: str, detail_text: str = ""):

View File

@ -1,19 +1,40 @@
from PySide6.QtGui import QGuiApplication, QAction
from PySide6.QtQml import QQmlApplicationEngine
from PySide6 import QtWidgets, QtCore
from PySide6.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QLabel
class BookEditor(QtWidgets.QWidget):
def __init__(self):
super().__init__()
from models.book_overview import BooksOverview
# Central widget and layout
main_layout = QtWidgets.QVBoxLayout(self)
class BookEditor(QDialog):
def __init__(self, book_overview: BooksOverview, parent=None):
super().__init__(parent)
# Title label
title_label = QtWidgets.QLabel("Books", self)
title_label.setAlignment(QtCore.Qt.AlignCenter)
title_label.setStyleSheet("font-size: 20px; font-weight: bold; color: #0078D4;")
main_layout.addWidget(title_label)
self.setWindowTitle("Edit a book")
self.setMinimumSize(400, 300)
self.setLayout(main_layout)
# Create layout
layout = QVBoxLayout(self)
data_mode_layout = QHBoxLayout()
self.data_mode_label = QLabel("Data mode:")
data_mode_layout.addWidget(self.data_mode_label)
# Buttons
button_layout = QtWidgets.QHBoxLayout()
self.save_button = QtWidgets.QPushButton("Save")
self.save_button.clicked.connect(self.save_book)
button_layout.addWidget(self.save_button)
self.cancel_button = QtWidgets.QPushButton("Discard")
self.cancel_button.clicked.connect(self.reject)
button_layout.addWidget(self.cancel_button)
layout.addLayout(button_layout)
def save_book(self):
pass

View File

@ -1,9 +1,11 @@
from PySide6.QtGui import QGuiApplication, QAction
from PySide6.QtGui import QGuiApplication, QAction, Qt
from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtWidgets import QHBoxLayout, QVBoxLayout, QLabel, QWidget, QMenu, QSizePolicy, QLayout
from PySide6.QtCore import qDebug
from ui.book_editor.book_editor import BookEditor
from models.book import BookStatusEnum
from models.book_overview import BooksOverview
STATUS_TO_COLOR_MAP = {
@ -11,22 +13,36 @@ STATUS_TO_COLOR_MAP = {
BookStatusEnum.borrowed: "#702525",
BookStatusEnum.reserved: "#bc7613"
}
class BookCard(QWidget):
def __init__(self, book_overview: BooksOverview):
super().__init__()
# Create the layout for the card
layout = QHBoxLayout(self)
layout.setSizeConstraint(QLayout.SizeConstraint.SetMinimumSize) # Ensure minimum size is respected
self.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed) # Avoid vertical stretching
self.book_overview = book_overview
layout.setContentsMargins(10, 10, 10, 10) # Optional: Add margins for spacing
self.setAttribute(Qt.WidgetAttribute.WA_Hover, True) # Enable hover events
self.setAttribute(Qt.WidgetAttribute.WA_StyledBackground, True) # Enable styling for background
# Set initial stylesheet with hover behavior
self.setStyleSheet("""
BookCard:hover {
background-color: palette(highlight);
}
""")
# Layout setup
layout = QHBoxLayout(self)
layout.setSizeConstraint(QLayout.SizeConstraint.SetMinimumSize)
self.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed)
layout.setContentsMargins(10, 10, 10, 10)
layout.setSpacing(10)
# Left-side content
left_side = QVBoxLayout()
layout.addLayout(left_side)
title_label = QLabel(book_overview.title)
title_label.setStyleSheet("font-size: 20px; font-weight: bold;")
author_label = QLabel("By: " + book_overview.author_name)
isbn_label = QLabel("ISBN: " + (book_overview.isbn or "Not Available"))
left_side.addWidget(title_label)
@ -36,33 +52,50 @@ class BookCard(QWidget):
# Right-side content
right_side = QVBoxLayout()
layout.addLayout(right_side)
status_label = QLabel(str(book_overview.status))
status_label.setStyleSheet(f"color: {STATUS_TO_COLOR_MAP[book_overview.status]}")
status_label = QLabel(str(book_overview.status.value.capitalize()))
status_label.setStyleSheet(f"color: {STATUS_TO_COLOR_MAP[book_overview.status]}; font-size: 20px; font-weight: bold;")
status_label.setAlignment(Qt.AlignmentFlag.AlignRight)
right_side.addWidget(status_label)
if book_overview.librarian_name and book_overview.borrower_name:
borrower_label = QLabel("Borrowed: " + book_overview.borrower_name)
borrower_label.setAlignment(Qt.AlignmentFlag.AlignRight)
librarian_label = QLabel("By: " + book_overview.librarian_name)
librarian_label.setAlignment(Qt.AlignmentFlag.AlignRight)
right_side.addWidget(borrower_label)
right_side.addWidget(librarian_label)
self.setLayout(layout)
def mousePressEvent(self, event):
if event.button() == Qt.MouseButton.LeftButton:
self.contextMenuEvent(event)
else:
super().mousePressEvent(event)
def contextMenuEvent(self, event):
"""Override to create a custom right-click menu."""
context_menu = QMenu(self)
# Add actions to the menu
action_edit_book = context_menu.addAction("Edit Book")
action_edit_author = context_menu.addAction("Edit Author")
action_mark_returned = context_menu.addAction("Mark as Returned")
# Execute the menu and get the selected action
if self.book_overview.status == BookStatusEnum.borrowed:
action_mark_returned = context_menu.addAction("Mark as Returned")
if self.book_overview.status == BookStatusEnum.reserved:
action_remove_reservation = context_menu.addAction("Remove reservation")
action = context_menu.exec_(self.mapToGlobal(event.pos()))
if action == action_edit_book:
self.label.setText("Edit Book selected")
BookEditor(self.book_overview).exec()
elif action == action_edit_author:
self.label.setText("Edit Author selected")
print("Edit Author selected")
elif action == action_mark_returned:
self.label.setText("Mark as Returned selected")
print("Mark as Returned selected")
elif action == action_remove_reservation:
print("Remove reservation selected")

View File

@ -1,15 +1,16 @@
from PySide6.QtGui import QAction
from PySide6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QScrollArea,
QFrame, QPushButton, QMessageBox, QVBoxLayout, QScrollArea
QFrame, QPushButton, QMessageBox, QVBoxLayout
)
from PySide6.QtCore import Qt
from .book_card import BookCard
from models.book_overview import BooksOverview
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from utils.database import DatabaseManager
class LibraryDashboard(QWidget):
def __init__(self):
super().__init__()
@ -20,7 +21,8 @@ class LibraryDashboard(QWidget):
# Title label
title_label = QLabel("Dashboard", self)
title_label.setAlignment(Qt.AlignCenter)
title_label.setStyleSheet("font-size: 20px; font-weight: bold; color: #0078D4;")
title_label.setStyleSheet(
"font-size: 20px; font-weight: bold; color: #0078D4;")
main_layout.addWidget(title_label)
# Search bar
@ -36,17 +38,21 @@ class LibraryDashboard(QWidget):
# Container widget for the scroll area
self.scroll_widget = QWidget()
self.scroll_layout = QVBoxLayout(self.scroll_widget)
self.scroll_layout.setSpacing(5) # Optional: Adjust spacing between cards
self.scroll_layout.setContentsMargins(0, 0, 0, 0)
self.scroll_layout.setSpacing(5) # Set gap between individual cards
self.scroll_layout.setContentsMargins(0, 0, 0, 0) # Remove spacing from all sides which is present by default
# Align the cards to the top
self.scroll_layout.setAlignment(Qt.AlignTop)
# Example cards
# self.books = self.fetch_books_from_db()
# self.book_cards = []
self.books = self.fetch_books_from_db()
self.book_cards = []
# for book in self.books:
# card = BookCard(book)
# self.scroll_layout.addWidget(card)
# self.book_cards.append(card)
for book in self.books:
card = BookCard(book)
self.scroll_layout.addWidget(card)
self.book_cards.append(card)
self.scroll_widget.setLayout(self.scroll_layout)
self.scroll_area.setWidget(self.scroll_widget)
@ -67,21 +73,23 @@ class LibraryDashboard(QWidget):
def filter_books(self, text):
"""Filter the cards based on the search input."""
for card, book in zip(self.book_cards, self.books):
card.setVisible(text.lower() in book["title"].lower() or text.lower() in book["description"].lower())
card.setVisible(text.lower() in book.title.lower())
def register_member(self):
QMessageBox.information(self, "Add Member", "Open dialog to register a new member.")
QMessageBox.information(
self, "Add Member", "Open dialog to register a new member.")
def add_borrow_record(self):
QMessageBox.information(self, "Add Borrow Record", "Open dialog to add a borrow record.")
QMessageBox.information(self, "Add Borrow Record",
"Open dialog to add a borrow record.")
def fetch_books_from_db(self):
"""Fetch all books from the database."""
try:
session = SessionLocal()
books = session.query(BooksOverview).all()
return books
with DatabaseManager.get_session() as session:
books = session.query(BooksOverview).all()
return books
except Exception as e:
QMessageBox.critical(self, "Database Error", f"Failed to fetch books: {e}")
QMessageBox.critical(self, "Database Error",
f"Failed to fetch books: {e}")
return []

View File

@ -11,12 +11,6 @@ class SettingsDialog(QtWidgets.QDialog):
self.setWindowTitle("Settings")
self.setMinimumSize(400, 100)
# Position the dialog relative to the parent (main window)
if parent:
x = parent.geometry().x() + parent.geometry().width() // 2 - self.width() // 2
y = parent.geometry().y() + parent.geometry().height() // 2 - self.height() // 2
self.move(x, y)
# Create layout
layout = QtWidgets.QVBoxLayout(self)

View File

@ -26,7 +26,7 @@ class LibraryWindow(QtWidgets.QMainWindow):
self.setCentralWidget(central_widget)
central_widget.addTab(LibraryDashboard(), "Dashboard")
central_widget.addTab(BookEditor(), "Books")
# central_widget.addTab(BookEditor(), "Books")
central_widget.addTab(MemberEditor(), "Members")
@ -74,6 +74,7 @@ class LibraryWindow(QtWidgets.QMainWindow):
# Edit menu
edit_menu = menu_bar.addMenu("Edit")
# Preferences menu
preferences_action = QAction("Preferences", self)
preferences_action.setShortcut("Ctrl+,")
preferences_action.triggered.connect(self.edit_preferences)

View File

@ -1,27 +1,33 @@
import logging
from mysql.connector import connect, cursor
from sqlalchemy.orm import sessionmaker, Session
from sqlalchemy import create_engine
from utils.config import DatabaseConfig
from utils.errors.database_config_error import DatabaseConfigError
class DatabaseManager():
_instance: 'DatabaseManager' = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self) -> None:
self.logger = logging.getLogger(__name__)
self.logger.info("Reading database config")
self.database_config = DatabaseConfig()
self.connection = connect(
host=self.database_config.host,
port=self.database_config.port,
database=self.database_config.name,
user=self.database_config.user,
password=self.database_config.password
)
def get_cursor(self) -> cursor.MySQLCursorAbstract:
return self.connection.cursor(dictionary=True)
self.engine = create_engine(f'mysql+mysqlconnector://{self.database_config.user}:{
self.database_config.password}@{self.database_config.host}/{self.database_config.name}')
self.session_local = sessionmaker(bind=self.engine)
def cleanup(self) -> None:
self.logger.debug("Closing connection")
self.connection.close()
self.engine.dispose()
@classmethod
def get_session(cls) -> Session:
return DatabaseManager._instance.session_local()