mirror of
https://github.com/kristoferssolo/LU-bookstore.git
synced 2025-10-21 18:00:34 +00:00
feat(table): display data as a table
This commit is contained in:
parent
73090ea440
commit
aa53cd3223
@ -6,7 +6,7 @@ authors = [{ name = "Kristofers Solo", email = "dev@kristofers.xyz" }]
|
|||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
license = { text = "MIT" }
|
license = { text = "MIT" }
|
||||||
dependencies = ["attrs==23.1.0", "urwid==2.2.3"]
|
dependencies = ["attrs==23.1.0", "npyscreen==4.10.5"]
|
||||||
|
|
||||||
[tool.mypy]
|
[tool.mypy]
|
||||||
check_untyped_defs = true
|
check_untyped_defs = true
|
||||||
|
|||||||
@ -1,2 +1,2 @@
|
|||||||
attrs>=23.1.0
|
attrs>=23.1.0
|
||||||
urwid>=2.2.3
|
npyscreen>=4.10.5
|
||||||
|
|||||||
@ -29,5 +29,5 @@ class Book:
|
|||||||
def fields(cls) -> tuple[str, str, str, str, str]:
|
def fields(cls) -> tuple[str, str, str, str, str]:
|
||||||
return "ISBN", "Title", "Author", "Price", "Stock"
|
return "ISBN", "Title", "Author", "Price", "Stock"
|
||||||
|
|
||||||
def field_values(self) -> tuple[ISBN, str, str, float, int]:
|
def values(self) -> tuple[ISBN, str, str, float, int]:
|
||||||
return self.isbn, self.title, self.author, self.price, self.stock
|
return self.isbn, self.title, self.author, self.price, self.stock
|
||||||
|
|||||||
@ -6,7 +6,11 @@ from attrs import define, field, validators
|
|||||||
class ISBN(str):
|
class ISBN(str):
|
||||||
number: str = field(converter=str, validator=validators.matches_re(r"^\d{10}$|^\d{13}$"))
|
number: str = field(converter=str, validator=validators.matches_re(r"^\d{10}$|^\d{13}$"))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return str(self)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if len(self.number) == 10:
|
return self.number
|
||||||
return f"{self.number[:9]}-{self.number[9:]}"
|
# if len(self.number) == 10:
|
||||||
return f"{self.number[:3]}-{self.number[3:4]}-{self.number[4:6]}-{self.number[6:12]}-{self.number[12:]}"
|
# return f"{self.number[:9]}-{self.number[9:]}"
|
||||||
|
# return f"{self.number[:3]}-{self.number[3:4]}-{self.number[4:6]}-{self.number[6:12]}-{self.number[12:]}"
|
||||||
|
|||||||
10
src/main.py
10
src/main.py
@ -1,15 +1,11 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from pathlib import Path
|
from ui.app import App
|
||||||
|
|
||||||
from bookstore.inventory import Inventory
|
|
||||||
from ui import tui
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
db_path = Path("db.sqlite3")
|
app = App()
|
||||||
inventory = Inventory(db_path)
|
app.run()
|
||||||
tui.render(inventory)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
9
src/ui/app.py
Normal file
9
src/ui/app.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import npyscreen as nps
|
||||||
|
|
||||||
|
from .forms import AddBookForm, BookForm
|
||||||
|
|
||||||
|
|
||||||
|
class App(nps.NPSAppManaged):
|
||||||
|
def onStart(self):
|
||||||
|
self.addForm("MAIN", BookForm, name="Bookstore")
|
||||||
|
self.addForm("ADD_BOOK", AddBookForm, name="Add Book")
|
||||||
24
src/ui/forms.py
Normal file
24
src/ui/forms.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import npyscreen as nps
|
||||||
|
from bookstore.book import Book
|
||||||
|
|
||||||
|
from .utils import INVENTORY
|
||||||
|
from .widget import BookGrid
|
||||||
|
|
||||||
|
|
||||||
|
class BookForm(nps.FormBaseNew):
|
||||||
|
def create(self):
|
||||||
|
self.grid = self.add_widget(BookGrid, columns=5, select_whole_line=True, col_titles=Book.fields())
|
||||||
|
self.grid.values = [book.values() for book in INVENTORY.list_all()]
|
||||||
|
|
||||||
|
|
||||||
|
class AddBookForm(nps.ActionForm):
|
||||||
|
def create(self):
|
||||||
|
self.isbn = self.add(nps.TitleText, name="ISBN:")
|
||||||
|
self.title = self.add(nps.TitleText, name="Title:")
|
||||||
|
self.author = self.add(nps.TitleText, name="Author:")
|
||||||
|
self.price = self.add(nps.TitleText, name="Price:")
|
||||||
|
self.stock = self.add(nps.TitleText, name="Stock:")
|
||||||
|
|
||||||
|
def on_ok(self):
|
||||||
|
book = Book(self.isbn.value, self.title.value, self.author.value, self.price.value, self.stock.value)
|
||||||
|
INVENTORY.add(book)
|
||||||
@ -1,7 +0,0 @@
|
|||||||
import urwid
|
|
||||||
|
|
||||||
|
|
||||||
def exit_on_q(key):
|
|
||||||
"""Define a function to handle exit when the `q` key is pressed"""
|
|
||||||
if key in ("q", "Q"):
|
|
||||||
raise urwid.ExitMainLoop()
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
import urwid
|
|
||||||
from bookstore.book import Book
|
|
||||||
from bookstore.inventory import Inventory
|
|
||||||
|
|
||||||
from .handlers import exit_on_q
|
|
||||||
|
|
||||||
from .widgets.table import Table
|
|
||||||
|
|
||||||
|
|
||||||
def create_row(cells) -> urwid.Columns:
|
|
||||||
return urwid.Columns([urwid.Text(str(cell)) for cell in cells])
|
|
||||||
|
|
||||||
|
|
||||||
def create_books_table(books: list[Book]) -> Table:
|
|
||||||
header = create_row(Book.fields())
|
|
||||||
rows = [create_row(book.field_values()) for book in books]
|
|
||||||
return Table(urwid.SimpleListWalker([header] + rows))
|
|
||||||
|
|
||||||
|
|
||||||
def render(inventory: Inventory):
|
|
||||||
books_table = create_books_table(inventory.list_all())
|
|
||||||
loop = urwid.MainLoop(books_table, unhandled_input=exit_on_q)
|
|
||||||
loop.run()
|
|
||||||
@ -1,64 +1,5 @@
|
|||||||
import customtkinter as ctk
|
from pathlib import Path
|
||||||
from bookstore.book import Book
|
|
||||||
from bookstore.inventory import Inventory
|
from bookstore.inventory import Inventory
|
||||||
|
|
||||||
|
INVENTORY = Inventory(Path("db.sqlite3"))
|
||||||
class UI:
|
|
||||||
def __init__(self, inventory: Inventory) -> None:
|
|
||||||
self.inventory = inventory
|
|
||||||
self.theme()
|
|
||||||
self.root = ctk.CTk()
|
|
||||||
self.root.geometry("650x400")
|
|
||||||
self.frame = ctk.CTkFrame(self.root)
|
|
||||||
|
|
||||||
def render(self) -> None:
|
|
||||||
self._create_gui()
|
|
||||||
self.root.mainloop()
|
|
||||||
|
|
||||||
def theme(self, mode: str = "dark", color: str = "dark-blue") -> None:
|
|
||||||
ctk.set_appearance_mode(mode)
|
|
||||||
ctk.set_default_color_theme(color)
|
|
||||||
|
|
||||||
def _create_gui(self) -> None:
|
|
||||||
self._show()
|
|
||||||
|
|
||||||
label = ctk.CTkLabel(self.frame, text="LU Bookstore")
|
|
||||||
label.pack(pady=12, padx=10)
|
|
||||||
|
|
||||||
button = ctk.CTkButton(self.frame, text="New book", command=self._add_book)
|
|
||||||
button.pack(pady=12, padx=10)
|
|
||||||
|
|
||||||
def _list_books(self) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def _add_book(self) -> None:
|
|
||||||
self._hide()
|
|
||||||
self._show()
|
|
||||||
|
|
||||||
placeholders = ("ISBN", "Title", "Author", "Price", "Stock")
|
|
||||||
self.entries: list[ctk.CTkEntry] = []
|
|
||||||
|
|
||||||
for placeholder in placeholders:
|
|
||||||
entry = ctk.CTkEntry(self.frame, placeholder_text=placeholder)
|
|
||||||
entry.pack(pady=12, padx=10)
|
|
||||||
self.entries.append(entry)
|
|
||||||
|
|
||||||
button = ctk.CTkButton(self.frame, text="Save", command=self._save_book)
|
|
||||||
button.pack(pady=12, padx=10)
|
|
||||||
|
|
||||||
def _save_book(self) -> None:
|
|
||||||
entry_values = [entry.get() for entry in self.entries]
|
|
||||||
if entry_values[0]: # ISBN must be a value
|
|
||||||
new_book = Book(*entry_values)
|
|
||||||
self.inventory.add(new_book)
|
|
||||||
|
|
||||||
for entry in self.entries:
|
|
||||||
entry.destroy()
|
|
||||||
self.entries = []
|
|
||||||
self._show()
|
|
||||||
|
|
||||||
def _hide(self) -> None:
|
|
||||||
self.frame.pack_forget()
|
|
||||||
|
|
||||||
def _show(self) -> None:
|
|
||||||
self.frame.pack(pady=20, padx=60, fill="both", expand=True)
|
|
||||||
|
|||||||
13
src/ui/widget.py
Normal file
13
src/ui/widget.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import npyscreen as nps
|
||||||
|
|
||||||
|
|
||||||
|
class BookGrid(nps.GridColTitles):
|
||||||
|
def custom_print_cell(self, actual_cell, cell_display_value):
|
||||||
|
if actual_cell == 4:
|
||||||
|
if int(cell_display_value) == 0:
|
||||||
|
actual_cell.color = "DANGER"
|
||||||
|
elif int(cell_display_value) < 5:
|
||||||
|
actual_cell.color = "WARNING"
|
||||||
|
else:
|
||||||
|
actual_cell.color = "DEFAULT"
|
||||||
|
return super().custom_print_cell(actual_cell, cell_display_value)
|
||||||
@ -1,6 +0,0 @@
|
|||||||
import urwid
|
|
||||||
|
|
||||||
|
|
||||||
class Table(urwid.ListBox):
|
|
||||||
def __init__(self, body):
|
|
||||||
super().__init__(body)
|
|
||||||
Loading…
Reference in New Issue
Block a user