Add a search bar and filters (by status, type, year, season) right above the table.
This commit is contained in:
parent
307cd2fd43
commit
6fc4f716b7
41
backend.py
41
backend.py
@ -30,10 +30,28 @@ class AnimeBackend:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error creating table: {e}")
|
logging.error(f"Error creating table: {e}")
|
||||||
|
|
||||||
def get_pre_2010_entries(self):
|
def get_pre_2010_entries(self, status_filter=None, type_filter=None, search=None, year_filter=None):
|
||||||
try:
|
try:
|
||||||
cursor = self.db.cursor()
|
cursor = self.db.cursor()
|
||||||
cursor.execute("SELECT * FROM anime WHERE year < 2010 ORDER BY year DESC, name")
|
sql = "SELECT * FROM anime WHERE year < 2010"
|
||||||
|
add_where = []
|
||||||
|
params = []
|
||||||
|
if year_filter is not None:
|
||||||
|
add_where.append("year = ?")
|
||||||
|
params.append(year_filter)
|
||||||
|
if status_filter is not None:
|
||||||
|
add_where.append("status = ?")
|
||||||
|
params.append(status_filter)
|
||||||
|
if type_filter is not None:
|
||||||
|
add_where.append("type = ?")
|
||||||
|
params.append(type_filter)
|
||||||
|
if search is not None:
|
||||||
|
add_where.append("(name LIKE ? OR comment LIKE ?)")
|
||||||
|
params.append(f"%{search}%")
|
||||||
|
params.append(f"%{search}%")
|
||||||
|
where_clause = " AND " + " AND ".join(add_where) if add_where else ""
|
||||||
|
sql += where_clause + " ORDER BY year DESC, name"
|
||||||
|
cursor.execute(sql, params)
|
||||||
return cursor.fetchall()
|
return cursor.fetchall()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error getting pre-2010 entries: {e}")
|
logging.error(f"Error getting pre-2010 entries: {e}")
|
||||||
@ -48,10 +66,25 @@ class AnimeBackend:
|
|||||||
logging.error(f"Error getting years: {e}")
|
logging.error(f"Error getting years: {e}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def get_entries_for_season(self, year, season):
|
def get_entries_for_season(self, year, season, status_filter=None, type_filter=None, search=None):
|
||||||
try:
|
try:
|
||||||
cursor = self.db.cursor()
|
cursor = self.db.cursor()
|
||||||
cursor.execute("SELECT * FROM anime WHERE year = ? AND season = ? ORDER BY name", (year, season))
|
sql = "SELECT * FROM anime WHERE year = ? AND season = ?"
|
||||||
|
params = [year, season]
|
||||||
|
add_where = []
|
||||||
|
if status_filter is not None:
|
||||||
|
add_where.append("status = ?")
|
||||||
|
params.append(status_filter)
|
||||||
|
if type_filter is not None:
|
||||||
|
add_where.append("type = ?")
|
||||||
|
params.append(type_filter)
|
||||||
|
if search is not None:
|
||||||
|
add_where.append("(name LIKE ? OR comment LIKE ?)")
|
||||||
|
params.append(f"%{search}%")
|
||||||
|
params.append(f"%{search}%")
|
||||||
|
where_clause = " AND " + " AND ".join(add_where) if add_where else ""
|
||||||
|
sql += where_clause + " ORDER BY name"
|
||||||
|
cursor.execute(sql, params)
|
||||||
return cursor.fetchall()
|
return cursor.fetchall()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error getting entries for season {season} in year {year}: {e}")
|
logging.error(f"Error getting entries for season {season} in year {year}: {e}")
|
||||||
|
88
frontend.py
88
frontend.py
@ -9,7 +9,7 @@ from PyQt5.QtWidgets import (
|
|||||||
QMainWindow, QTabWidget, QScrollArea, QWidget, QVBoxLayout, QTableWidget, QTableWidgetItem,
|
QMainWindow, QTabWidget, QScrollArea, QWidget, QVBoxLayout, QTableWidget, QTableWidgetItem,
|
||||||
QLabel, QToolButton, QHBoxLayout, QDialog, QFormLayout, QLineEdit, QSpinBox,
|
QLabel, QToolButton, QHBoxLayout, QDialog, QFormLayout, QLineEdit, QSpinBox,
|
||||||
QComboBox, QTextEdit, QDialogButtonBox, QAction, QFileDialog, QMessageBox,
|
QComboBox, QTextEdit, QDialogButtonBox, QAction, QFileDialog, QMessageBox,
|
||||||
QInputDialog, QApplication, QAbstractItemView, QSizePolicy, QHeaderView
|
QInputDialog, QApplication, QAbstractItemView, QSizePolicy, QHeaderView, QPushButton
|
||||||
)
|
)
|
||||||
from PyQt5.QtCore import Qt, QSettings
|
from PyQt5.QtCore import Qt, QSettings
|
||||||
from PyQt5.QtGui import QColor, QIcon
|
from PyQt5.QtGui import QColor, QIcon
|
||||||
@ -119,8 +119,6 @@ class CustomTableWidget(QTableWidget):
|
|||||||
if selected_rows:
|
if selected_rows:
|
||||||
row = selected_rows[0].row()
|
row = selected_rows[0].row()
|
||||||
anime_id = int(self.item(row, 0).text())
|
anime_id = int(self.item(row, 0).text())
|
||||||
status_col = 4 if self.is_pre else 3
|
|
||||||
status = self.item(row, status_col).text()
|
|
||||||
if key == Qt.Key_Delete:
|
if key == Qt.Key_Delete:
|
||||||
self.parent_window.delete_anime(anime_id)
|
self.parent_window.delete_anime(anime_id)
|
||||||
return
|
return
|
||||||
@ -128,11 +126,15 @@ class CustomTableWidget(QTableWidget):
|
|||||||
self.parent_window.edit_anime(anime_id)
|
self.parent_window.edit_anime(anime_id)
|
||||||
return
|
return
|
||||||
elif key == Qt.Key_W:
|
elif key == Qt.Key_W:
|
||||||
new_status = 'watching' if status == 'unwatched' else 'unwatched'
|
status_col = 4 if self.is_pre else 3
|
||||||
|
status = self.item(row, status_col).text()
|
||||||
|
new_status = 'watching' if status != 'watching' else 'unwatched'
|
||||||
self.parent_window.change_status(anime_id, new_status)
|
self.parent_window.change_status(anime_id, new_status)
|
||||||
return
|
return
|
||||||
elif key == Qt.Key_C:
|
elif key == Qt.Key_C:
|
||||||
new_status = 'completed' if status == 'unwatched' else 'unwatched'
|
status_col = 4 if self.is_pre else 3
|
||||||
|
status = self.item(row, status_col).text()
|
||||||
|
new_status = 'completed' if status != 'completed' else 'unwatched'
|
||||||
self.parent_window.change_status(anime_id, new_status)
|
self.parent_window.change_status(anime_id, new_status)
|
||||||
return
|
return
|
||||||
super().keyPressEvent(event)
|
super().keyPressEvent(event)
|
||||||
@ -145,7 +147,48 @@ class AnimeTracker(QMainWindow):
|
|||||||
self.resize(800, 600)
|
self.resize(800, 600)
|
||||||
self.backend = AnimeBackend()
|
self.backend = AnimeBackend()
|
||||||
self.tab_widget = QTabWidget()
|
self.tab_widget = QTabWidget()
|
||||||
self.setCentralWidget(self.tab_widget)
|
self.central_widget = QWidget()
|
||||||
|
central_layout = QVBoxLayout(self.central_widget)
|
||||||
|
self.filter_bar = QWidget()
|
||||||
|
filter_layout = QHBoxLayout(self.filter_bar)
|
||||||
|
label_year = QLabel("Year:")
|
||||||
|
self.year_spin = QSpinBox()
|
||||||
|
self.year_spin.setRange(0, 2100)
|
||||||
|
self.year_spin.setValue(0)
|
||||||
|
self.year_spin.setSpecialValueText("All")
|
||||||
|
filter_layout.addWidget(label_year)
|
||||||
|
filter_layout.addWidget(self.year_spin)
|
||||||
|
label_season = QLabel("Season:")
|
||||||
|
self.season_combo = QComboBox()
|
||||||
|
self.season_combo.addItems(['All', 'winter', 'spring', 'summer', 'fall', 'Other'])
|
||||||
|
filter_layout.addWidget(label_season)
|
||||||
|
filter_layout.addWidget(self.season_combo)
|
||||||
|
label_status = QLabel("Status:")
|
||||||
|
self.status_combo = QComboBox()
|
||||||
|
self.status_combo.addItems(['All', 'unwatched', 'watching', 'completed'])
|
||||||
|
filter_layout.addWidget(label_status)
|
||||||
|
filter_layout.addWidget(self.status_combo)
|
||||||
|
label_type = QLabel("Type:")
|
||||||
|
self.type_combo = QComboBox()
|
||||||
|
self.type_combo.addItems(['All', 'TV', 'Movie', 'OVA', 'Special', 'Short TV', 'Other'])
|
||||||
|
filter_layout.addWidget(label_type)
|
||||||
|
filter_layout.addWidget(self.type_combo)
|
||||||
|
label_search = QLabel("Search:")
|
||||||
|
self.search_edit = QLineEdit()
|
||||||
|
self.search_edit.setPlaceholderText("Search name or comment")
|
||||||
|
filter_layout.addWidget(label_search)
|
||||||
|
filter_layout.addWidget(self.search_edit)
|
||||||
|
apply_btn = QPushButton("Apply")
|
||||||
|
apply_btn.clicked.connect(self.apply_filters)
|
||||||
|
filter_layout.addWidget(apply_btn)
|
||||||
|
central_layout.addWidget(self.filter_bar)
|
||||||
|
central_layout.addWidget(self.tab_widget)
|
||||||
|
self.setCentralWidget(self.central_widget)
|
||||||
|
self.year_filter = None
|
||||||
|
self.season_filter = None
|
||||||
|
self.status_filter = None
|
||||||
|
self.type_filter = None
|
||||||
|
self.search_text = None
|
||||||
self.create_menu()
|
self.create_menu()
|
||||||
self.load_tabs()
|
self.load_tabs()
|
||||||
self.restoreGeometry(self.settings.value("geometry", self.saveGeometry()))
|
self.restoreGeometry(self.settings.value("geometry", self.saveGeometry()))
|
||||||
@ -153,6 +196,19 @@ class AnimeTracker(QMainWindow):
|
|||||||
if last_tab is not None:
|
if last_tab is not None:
|
||||||
self.set_current_tab_by_identifier(last_tab)
|
self.set_current_tab_by_identifier(last_tab)
|
||||||
|
|
||||||
|
def apply_filters(self):
|
||||||
|
year_val = self.year_spin.value()
|
||||||
|
self.year_filter = year_val if year_val > 0 else None
|
||||||
|
season_val = self.season_combo.currentText()
|
||||||
|
self.season_filter = None if season_val == 'All' else ('' if season_val == 'Other' else season_val)
|
||||||
|
status_val = self.status_combo.currentText()
|
||||||
|
self.status_filter = None if status_val == 'All' else status_val
|
||||||
|
type_val = self.type_combo.currentText()
|
||||||
|
self.type_filter = None if type_val == 'All' else type_val
|
||||||
|
search_val = self.search_edit.text().strip()
|
||||||
|
self.search_text = search_val if search_val else None
|
||||||
|
self.load_tabs()
|
||||||
|
|
||||||
def closeEvent(self, event):
|
def closeEvent(self, event):
|
||||||
self.settings.setValue("geometry", self.saveGeometry())
|
self.settings.setValue("geometry", self.saveGeometry())
|
||||||
self.settings.setValue("lastTab", self.get_current_tab_identifier())
|
self.settings.setValue("lastTab", self.get_current_tab_identifier())
|
||||||
@ -220,8 +276,13 @@ class AnimeTracker(QMainWindow):
|
|||||||
|
|
||||||
def load_tabs(self):
|
def load_tabs(self):
|
||||||
self.tab_widget.clear()
|
self.tab_widget.clear()
|
||||||
# Pre-2010 tab
|
show_pre = True
|
||||||
pre_entries = self.backend.get_pre_2010_entries()
|
if self.season_filter is not None and self.season_filter != '':
|
||||||
|
show_pre = False
|
||||||
|
if self.year_filter is not None and self.year_filter >= 2010:
|
||||||
|
show_pre = False
|
||||||
|
if show_pre:
|
||||||
|
pre_entries = self.backend.get_pre_2010_entries(self.status_filter, self.type_filter, self.search_text, self.year_filter if self.year_filter and self.year_filter < 2010 else None)
|
||||||
pre_tab = QScrollArea()
|
pre_tab = QScrollArea()
|
||||||
pre_tab.setWidgetResizable(True)
|
pre_tab.setWidgetResizable(True)
|
||||||
pre_content = QWidget()
|
pre_content = QWidget()
|
||||||
@ -328,6 +389,8 @@ class AnimeTracker(QMainWindow):
|
|||||||
self.tab_widget.tabBar().setTabTextColor(index, QColor('gray'))
|
self.tab_widget.tabBar().setTabTextColor(index, QColor('gray'))
|
||||||
# Years >= 2010
|
# Years >= 2010
|
||||||
years = self.backend.get_years()
|
years = self.backend.get_years()
|
||||||
|
if self.year_filter is not None:
|
||||||
|
years = [self.year_filter] if self.year_filter in years else []
|
||||||
for year in years:
|
for year in years:
|
||||||
year_tab = QScrollArea()
|
year_tab = QScrollArea()
|
||||||
year_tab.setWidgetResizable(True)
|
year_tab.setWidgetResizable(True)
|
||||||
@ -336,7 +399,12 @@ class AnimeTracker(QMainWindow):
|
|||||||
total_entries = 0
|
total_entries = 0
|
||||||
comp_entries = 0
|
comp_entries = 0
|
||||||
for season in ['winter', 'spring', 'summer', 'fall', '']:
|
for season in ['winter', 'spring', 'summer', 'fall', '']:
|
||||||
entries = self.backend.get_entries_for_season(year, season)
|
show_section = True
|
||||||
|
if self.season_filter is not None:
|
||||||
|
if season != self.season_filter:
|
||||||
|
show_section = False
|
||||||
|
if show_section:
|
||||||
|
entries = self.backend.get_entries_for_season(year, season, self.status_filter, self.type_filter, self.search_text)
|
||||||
if entries:
|
if entries:
|
||||||
s_name = season.capitalize() if season else 'Other'
|
s_name = season.capitalize() if season else 'Other'
|
||||||
label = QLabel(s_name)
|
label = QLabel(s_name)
|
||||||
@ -425,7 +493,7 @@ class AnimeTracker(QMainWindow):
|
|||||||
total_entries += len(entries)
|
total_entries += len(entries)
|
||||||
comp_entries += sum(1 for e in entries if e[4] == 'completed')
|
comp_entries += sum(1 for e in entries if e[4] == 'completed')
|
||||||
year_tab.setWidget(content)
|
year_tab.setWidget(content)
|
||||||
if total_entries > 0:
|
if total_entries > 0 or (self.year_filter == year):
|
||||||
perc = (comp_entries / total_entries * 100) if total_entries > 0 else 0
|
perc = (comp_entries / total_entries * 100) if total_entries > 0 else 0
|
||||||
tab_text = f"{year} ({perc:.0f}%)"
|
tab_text = f"{year} ({perc:.0f}%)"
|
||||||
completed = (comp_entries == total_entries)
|
completed = (comp_entries == total_entries)
|
||||||
|
Loading…
Reference in New Issue
Block a user