Compare commits

..

No commits in common. "12828b268851dd07f324292b756638871a3905dc" and "a2ffd8aa3b8717571f93b501d5e8340f754dc3fe" have entirely different histories.

8 changed files with 74 additions and 139 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -11,6 +11,12 @@ class SettingsDialog(QtWidgets.QDialog):
self.setWindowTitle("Settings") self.setWindowTitle("Settings")
self.setMinimumSize(400, 100) 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 # Create layout
layout = QtWidgets.QVBoxLayout(self) layout = QtWidgets.QVBoxLayout(self)

View File

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

View File

@ -1,33 +1,27 @@
import logging import logging
from sqlalchemy.orm import sessionmaker, Session from mysql.connector import connect, cursor
from sqlalchemy import create_engine
from utils.config import DatabaseConfig from utils.config import DatabaseConfig
from utils.errors.database_config_error import DatabaseConfigError from utils.errors.database_config_error import DatabaseConfigError
class DatabaseManager(): 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: def __init__(self) -> None:
self.logger = logging.getLogger(__name__) self.logger = logging.getLogger(__name__)
self.logger.info("Reading database config") self.logger.info("Reading database config")
self.database_config = DatabaseConfig() self.database_config = DatabaseConfig()
self.engine = create_engine(f'mysql+mysqlconnector://{self.database_config.user}:{ self.connection = connect(
self.database_config.password}@{self.database_config.host}/{self.database_config.name}') host=self.database_config.host,
self.session_local = sessionmaker(bind=self.engine) 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)
def cleanup(self) -> None: def cleanup(self) -> None:
self.logger.debug("Closing connection") self.logger.debug("Closing connection")
self.engine.dispose() self.connection.close()
@classmethod
def get_session(cls) -> Session:
return DatabaseManager._instance.session_local()