185 lines
5.9 KiB
Python
185 lines
5.9 KiB
Python
import sys
|
|
import typing
|
|
|
|
import pandas as pd
|
|
from PyQt6 import uic, QtGui, QtCore
|
|
from PyQt6.QtCore import Qt, QSortFilterProxyModel
|
|
from PyQt6.QtGui import QPixmap, QAction
|
|
from PyQt6.QtWidgets import QMainWindow, QPushButton, QHBoxLayout, QLabel, \
|
|
QVBoxLayout, QMenu, QApplication, QTableView, QListView
|
|
|
|
|
|
class PandasModel(QtCore.QAbstractTableModel):
|
|
def __init__(self, df: pd.DataFrame, parent=None):
|
|
QtCore.QAbstractTableModel.__init__(self, parent)
|
|
self._data = df
|
|
|
|
self._horizontalHeaders = [''] * len(df.columns)
|
|
|
|
for i, column in enumerate(df.columns):
|
|
self.setHeaderData(i, Qt.Orientation.Horizontal, column)
|
|
|
|
def setHeaderData(self, section, orientation, data,
|
|
role=Qt.ItemDataRole.EditRole):
|
|
|
|
if ((orientation == Qt.Orientation.Horizontal)
|
|
and ((role == Qt.ItemDataRole.DisplayRole)
|
|
or (role == Qt.ItemDataRole.EditRole))):
|
|
|
|
self._horizontalHeaders[section] = data
|
|
return True
|
|
else:
|
|
return super().setHeaderData(section, orientation, data, role)
|
|
|
|
def headerData(self, section, orientation,
|
|
role=Qt.ItemDataRole.DisplayRole):
|
|
|
|
if (orientation == Qt.Orientation.Horizontal
|
|
and role == Qt.ItemDataRole.DisplayRole):
|
|
|
|
return self._horizontalHeaders[section]
|
|
else:
|
|
return super().headerData(section, orientation, role)
|
|
|
|
def rowCount(self, parent=None):
|
|
return len(self._data.values)
|
|
|
|
def columnCount(self, parent=None):
|
|
return self._data.columns.size
|
|
|
|
def data(self, index, role=Qt.ItemDataRole.DisplayRole):
|
|
if not index.isValid():
|
|
return QtCore.QVariant()
|
|
if role != Qt.ItemDataRole.DisplayRole:
|
|
return QtCore.QVariant()
|
|
|
|
item = self._data.iloc[index.row()].iloc[index.column()]
|
|
|
|
if type(item) is pd.Timestamp:
|
|
return QtCore.QVariant(item.strftime('%Y-%m-%d'))
|
|
else:
|
|
return QtCore.QVariant(str(item))
|
|
|
|
|
|
class MainWindow(QMainWindow):
|
|
def __init__(self):
|
|
super(MainWindow, self).__init__()
|
|
uic.loadUi("res/main_window.ui", self)
|
|
self.setContentsMargins(9, 9, 9, 9)
|
|
|
|
self._warning_layout \
|
|
= self.findChild(QVBoxLayout, "warningLayout")
|
|
|
|
self._create_button \
|
|
= self.findChild(QPushButton, "createCategoryButton")
|
|
self._delete_button \
|
|
= self.findChild(QPushButton, "deleteCategoryButton")
|
|
self._apply_button \
|
|
= self.findChild(QPushButton, "applyCategoryButton")
|
|
|
|
self._list_view \
|
|
= self.findChild(QListView, "categoryListView")
|
|
|
|
self._table_view \
|
|
= self.findChild(QTableView, "transactionTableView")
|
|
|
|
self._category_model = QtGui.QStandardItemModel()
|
|
self._list_view.setModel(self._category_model)
|
|
|
|
self._create_button.clicked.connect(
|
|
self._handle_create_click)
|
|
|
|
header = self._table_view.horizontalHeader()
|
|
header.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
|
|
header.customContextMenuRequested.connect(self._header_right_clicked)
|
|
|
|
def add_warning_text(self, text: str):
|
|
layout = QHBoxLayout()
|
|
|
|
warningIcon = QLabel()
|
|
pixmap = QPixmap("res/warning.png")
|
|
warningIcon.setPixmap(pixmap)
|
|
|
|
label = QLabel(text)
|
|
label.setWordWrap(True)
|
|
|
|
layout.addWidget(warningIcon)
|
|
layout.addWidget(label)
|
|
|
|
layout.setStretch(0, 0)
|
|
layout.setStretch(1, 1)
|
|
|
|
self._warning_layout.addLayout(layout)
|
|
|
|
def set_statement_data(self, df: pd.DataFrame):
|
|
model = PandasModel(df)
|
|
proxyModel = QSortFilterProxyModel(self)
|
|
proxyModel.setSourceModel(model)
|
|
self._table_view.setModel(proxyModel)
|
|
|
|
self._table_view.resizeColumnsToContents()
|
|
|
|
def add_categories(self, categories: typing.Sequence[str]):
|
|
for category in categories:
|
|
item = QtGui.QStandardItem(category)
|
|
self._category_model.appendRow(item)
|
|
|
|
def _header_right_clicked(self, pos):
|
|
context = QMenu(self)
|
|
context.addAction(QAction("test 1", self))
|
|
context.addAction(QAction("test 2", self))
|
|
context.addAction(QAction("test 3", self))
|
|
context.exec(self.sender().mapToGlobal(pos))
|
|
|
|
def _handle_create_click(self):
|
|
self.add_categories(['asdf'])
|
|
|
|
def _handle_delete_click(self):
|
|
pass
|
|
|
|
def _handle_apply_click(self):
|
|
pass
|
|
|
|
|
|
def show_main_window(categories: typing.Sequence[str] = None,
|
|
df: pd.DataFrame = None):
|
|
app = QApplication(sys.argv)
|
|
window = MainWindow()
|
|
|
|
if categories is not None:
|
|
window.add_categories(categories)
|
|
|
|
if df is not None:
|
|
window.set_statement_data(df)
|
|
|
|
window.add_warning_text("The column 't' does not exist. Please rename the"
|
|
" column containing the dates of the transactions"
|
|
" to 't'.")
|
|
window.add_warning_text("The column 'balance' does not exist. Please"
|
|
" rename the column containing the balance after"
|
|
" each transaction to 'balance'")
|
|
window.add_warning_text("The column 'value' does not exist. Please rename"
|
|
" the column containing the values of the"
|
|
" transactions to 'value'.")
|
|
|
|
window.show()
|
|
app.exec()
|
|
|
|
|
|
def main():
|
|
from banking_breakdown.statement_parser import get_stripped_statement
|
|
import os
|
|
os.chdir("../")
|
|
|
|
categories = ["Food", "Rent & Utilities", "Hobbies", "Education",
|
|
"Transportation", "Social Life", "Other"]
|
|
|
|
df = pd.read_csv("res/bank_statement_2023.csv", delimiter=';')
|
|
# df = get_stripped_statement("res/bank_statement_2023.csv")
|
|
|
|
show_main_window(categories, df)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|