Compare commits
2 Commits
a2ffd8aa3b
...
12828b2688
Author | SHA1 | Date | |
---|---|---|---|
12828b2688 | |||
16ebca7767 |
@ -1,4 +1,5 @@
|
|||||||
DB_USER=
|
DATABASE_HOST=
|
||||||
DB_NAME=
|
DATABASE_PORT=
|
||||||
DB_PASSWORD=
|
DATABASE_NAME=
|
||||||
DB_HOST=
|
DATABASE_USER=
|
||||||
|
DATABASE_PASSWORD=
|
@ -1,6 +1,7 @@
|
|||||||
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
|
||||||
|
|
||||||
@ -13,14 +14,14 @@ from ui.window import LibraryWindow
|
|||||||
|
|
||||||
|
|
||||||
class LibraryApp():
|
class LibraryApp():
|
||||||
def __init__(self, user_config: UserConfig | None = None):
|
def __init__(self, user_config: Optional[UserConfig] = None):
|
||||||
setup_logger()
|
setup_logger()
|
||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger(__name__)
|
||||||
self.logger.info("Starting")
|
self.logger.info("Starting")
|
||||||
|
|
||||||
self.qt_app = QApplication([])
|
self.qt_app = QApplication([])
|
||||||
self.user_config = user_config
|
self.user_config = user_config
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.database_manger = DatabaseManager()
|
self.database_manger = DatabaseManager()
|
||||||
except DatabaseConfigError as e:
|
except DatabaseConfigError as e:
|
||||||
@ -37,7 +38,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("Exitting")
|
self.logger.info("Exiting")
|
||||||
return status
|
return status
|
||||||
|
|
||||||
def show_error(self, text: str, detail_text: str = ""):
|
def show_error(self, text: str, detail_text: str = ""):
|
||||||
|
@ -1,19 +1,40 @@
|
|||||||
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
|
||||||
|
|
||||||
class BookEditor(QtWidgets.QWidget):
|
from models.book_overview import BooksOverview
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
# Central widget and layout
|
class BookEditor(QDialog):
|
||||||
main_layout = QtWidgets.QVBoxLayout(self)
|
def __init__(self, book_overview: BooksOverview, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
# Title label
|
self.setWindowTitle("Edit a book")
|
||||||
title_label = QtWidgets.QLabel("Books", self)
|
self.setMinimumSize(400, 300)
|
||||||
title_label.setAlignment(QtCore.Qt.AlignCenter)
|
|
||||||
title_label.setStyleSheet("font-size: 20px; font-weight: bold; color: #0078D4;")
|
|
||||||
main_layout.addWidget(title_label)
|
|
||||||
|
|
||||||
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
|
@ -1,9 +1,11 @@
|
|||||||
from PySide6.QtGui import QGuiApplication, QAction
|
from PySide6.QtGui import QGuiApplication, QAction, Qt
|
||||||
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 = {
|
||||||
@ -11,22 +13,36 @@ 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__()
|
||||||
|
|
||||||
# Create the layout for the card
|
self.book_overview = book_overview
|
||||||
layout = QHBoxLayout(self)
|
|
||||||
layout.setSizeConstraint(QLayout.SizeConstraint.SetMinimumSize) # Ensure minimum size is respected
|
|
||||||
self.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed) # Avoid vertical stretching
|
|
||||||
|
|
||||||
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)
|
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)
|
||||||
@ -36,33 +52,50 @@ 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.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)
|
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")
|
||||||
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()))
|
action = context_menu.exec_(self.mapToGlobal(event.pos()))
|
||||||
|
|
||||||
if action == action_edit_book:
|
if action == action_edit_book:
|
||||||
self.label.setText("Edit Book selected")
|
BookEditor(self.book_overview).exec()
|
||||||
elif action == action_edit_author:
|
elif action == action_edit_author:
|
||||||
self.label.setText("Edit Author selected")
|
print("Edit Author selected")
|
||||||
elif action == action_mark_returned:
|
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")
|
@ -1,15 +1,16 @@
|
|||||||
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, QScrollArea
|
QFrame, QPushButton, QMessageBox, QVBoxLayout
|
||||||
)
|
)
|
||||||
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 sqlalchemy import create_engine
|
from utils.database import DatabaseManager
|
||||||
from sqlalchemy.orm import sessionmaker
|
|
||||||
|
|
||||||
class LibraryDashboard(QWidget):
|
class LibraryDashboard(QWidget):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@ -20,7 +21,8 @@ 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("font-size: 20px; font-weight: bold; color: #0078D4;")
|
title_label.setStyleSheet(
|
||||||
|
"font-size: 20px; font-weight: bold; color: #0078D4;")
|
||||||
main_layout.addWidget(title_label)
|
main_layout.addWidget(title_label)
|
||||||
|
|
||||||
# Search bar
|
# Search bar
|
||||||
@ -36,17 +38,21 @@ 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) # Optional: Adjust spacing between cards
|
self.scroll_layout.setSpacing(5) # Set gap between individual cards
|
||||||
self.scroll_layout.setContentsMargins(0, 0, 0, 0)
|
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
|
# 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.book_cards.append(card)
|
self.scroll_layout.addWidget(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)
|
||||||
@ -67,21 +73,23 @@ 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() or text.lower() in book["description"].lower())
|
card.setVisible(text.lower() in book.title.lower())
|
||||||
|
|
||||||
def register_member(self):
|
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):
|
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):
|
def fetch_books_from_db(self):
|
||||||
|
|
||||||
"""Fetch all books from the database."""
|
"""Fetch all books from the database."""
|
||||||
try:
|
try:
|
||||||
session = SessionLocal()
|
with DatabaseManager.get_session() as session:
|
||||||
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", f"Failed to fetch books: {e}")
|
QMessageBox.critical(self, "Database Error",
|
||||||
return []
|
f"Failed to fetch books: {e}")
|
||||||
|
return []
|
||||||
|
@ -11,12 +11,6 @@ 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)
|
||||||
|
|
||||||
|
@ -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,6 +74,7 @@ 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)
|
||||||
|
@ -1,27 +1,33 @@
|
|||||||
import logging
|
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.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.connection = connect(
|
self.engine = create_engine(f'mysql+mysqlconnector://{self.database_config.user}:{
|
||||||
host=self.database_config.host,
|
self.database_config.password}@{self.database_config.host}/{self.database_config.name}')
|
||||||
port=self.database_config.port,
|
self.session_local = sessionmaker(bind=self.engine)
|
||||||
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.connection.close()
|
self.engine.dispose()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_session(cls) -> Session:
|
||||||
|
return DatabaseManager._instance.session_local()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user