diff options
| author | Kaufmann <Kau@avh.de> | 2025-04-02 11:16:56 +0200 |
|---|---|---|
| committer | Kaufmann <Kau@avh.de> | 2025-04-02 11:16:56 +0200 |
| commit | d62c4b9b194ad6233107cf71a87870441b2b0d4b (patch) | |
| tree | c9934e2f10022e69c38a639500b1fec64105cf8e /calendar_gui.py | |
Vorstellung des Ausfallzeitenrechners im JF von Abt. 2.5.
Initiale Kritik: Kommentarfeld, speichern als PDF, Datumsformat
Diffstat (limited to 'calendar_gui.py')
| -rw-r--r-- | calendar_gui.py | 446 |
1 files changed, 446 insertions, 0 deletions
diff --git a/calendar_gui.py b/calendar_gui.py new file mode 100644 index 0000000..8102a36 --- /dev/null +++ b/calendar_gui.py @@ -0,0 +1,446 @@ +import sys +from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, + QLabel, QLineEdit, QPushButton, QDateEdit, QTableWidget, + QTableWidgetItem, QHeaderView, QDialog, QFormLayout, QComboBox, + QMessageBox, QSpinBox, QAction, QFileDialog, QMenuBar) +from PyQt5.QtCore import Qt, QDate +from PyQt5.QtGui import QFont +from datetime import datetime, timedelta +from dateutil.relativedelta import relativedelta +from calendar_manager import CalendarManager +from date_calculator import DateCalculator +from prediction_controller import PredictionController + +class AddEventDialog(QDialog): + def __init__(self, keyword_list, parent=None): + super().__init__(parent) + self.setWindowTitle("Neuer Eintrag") + self.keyword_list = keyword_list + self.init_ui() + + def init_ui(self): + layout = QFormLayout() + + # Start date selector + self.start_date = QDateEdit() + self.start_date.setCalendarPopup(True) + self.start_date.setDate(QDate.currentDate()) + layout.addRow("Startdatum:", self.start_date) + + # End date selector + self.end_date = QDateEdit() + self.end_date.setCalendarPopup(True) + self.end_date.setDate(QDate.currentDate().addDays(1)) + layout.addRow("Enddatum:", self.end_date) + + # Keyword selector + self.keyword = QComboBox() + self.keyword.addItems(self.keyword_list) + self.keyword.currentTextChanged.connect(self.on_keyword_changed) + layout.addRow("Art:", self.keyword) + + # Store layout for later access + self.layout = layout + self.end_date_row = 1 # Index of end date row in the form layout + # Buttons + button_layout = QHBoxLayout() + self.save_button = QPushButton("Speichern") + self.save_button.clicked.connect(self.accept) + self.cancel_button = QPushButton("Abbrechen") + self.cancel_button.clicked.connect(self.reject) + + button_layout.addWidget(self.save_button) + button_layout.addWidget(self.cancel_button) + layout.addRow("", button_layout) + + self.setLayout(layout) + + # Handle initial keyword selection + self.on_keyword_changed(self.keyword.currentText()) + + def on_keyword_changed(self, keyword): + """Handle visibility of the end_date field based on keyword""" + # Get the widgets from the form layout + end_date_label = self.layout.itemAt(self.end_date_row, QFormLayout.LabelRole).widget() + end_date_field = self.layout.itemAt(self.end_date_row, QFormLayout.FieldRole).widget() + + if keyword == "EZ pauschal": + # Hide end date field for EZ pauschals since they have fixed 4-week duration + end_date_label.setVisible(False) + end_date_field.setVisible(False) + else: + # Show end date field for other event types + end_date_label.setVisible(True) + end_date_field.setVisible(True) + def get_data(self): + start_date = self.start_date.date().toString("yyyy-MM-dd") + keyword = self.keyword.currentText() + + if keyword == "EZ pauschal": + # For EZ pauschals, calculate end date as start + 4 weeks + start_dt = datetime.fromisoformat(start_date) + end_dt = start_dt + relativedelta(years = 2, days = -1) + end_date = end_dt.strftime("%Y-%m-%d") + else: + end_date = self.end_date.date().toString("yyyy-MM-dd") + return start_date, end_date, keyword + + +class ModifyEventDialog(QDialog): + def __init__(self, entry, keyword_list, parent=None): + super().__init__(parent) + self.setWindowTitle("Eintrag editieren") + self.entry = entry + self.keyword_list = keyword_list + self.init_ui() + + def init_ui(self): + layout = QFormLayout() + + # Start date selector + self.start_date = QDateEdit() + self.start_date.setCalendarPopup(True) + entry_start = QDate(self.entry.start_date.year, + self.entry.start_date.month, + self.entry.start_date.day) + self.start_date.setDate(entry_start) + layout.addRow("Startdatum:", self.start_date) + + # End date selector + self.end_date = QDateEdit() + self.end_date.setCalendarPopup(True) + entry_end = QDate(self.entry.end_date.year, + self.entry.end_date.month, + self.entry.end_date.day) + self.end_date.setDate(entry_end) + layout.addRow("Enddatum:", self.end_date) + + # Keyword selector + self.keyword = QComboBox() + self.keyword.addItems(self.keyword_list) + current_index = self.keyword_list.index(self.entry.keyword) if self.entry.keyword in self.keyword_list else 0 + self.keyword.setCurrentIndex(current_index) + self.keyword.currentTextChanged.connect(self.on_keyword_changed) + layout.addRow("Art:", self.keyword) + # Store layout for later access + self.layout = layout + self.end_date_row = 1 # Index of end date row in the form layout + + # Buttons + button_layout = QHBoxLayout() + self.save_button = QPushButton("Speichern") + self.save_button.clicked.connect(self.accept) + self.cancel_button = QPushButton("Abbrechen") + self.cancel_button.clicked.connect(self.reject) + + button_layout.addWidget(self.save_button) + button_layout.addWidget(self.cancel_button) + layout.addRow("", button_layout) + + self.setLayout(layout) + + # Handle initial keyword selection + self.on_keyword_changed(self.keyword.currentText()) + + def on_keyword_changed(self, keyword): + """Handle visibility of the end_date field based on keyword""" + # Get the widgets from the form layout + end_date_label = self.layout.itemAt(self.end_date_row, QFormLayout.LabelRole).widget() + end_date_field = self.layout.itemAt(self.end_date_row, QFormLayout.FieldRole).widget() + + if keyword == "EZ pauschal": + # Hide end date field for EZ pauschals since they have fixed 4-week duration + end_date_label.setVisible(False) + end_date_field.setVisible(False) + + # Update end date automatically if changing to EZ pauschal + start_dt = self.start_date.date().toPyDate() + end_dt = start_dt + relativedelta(years = 2, days = -1) + self.end_date.setDate(QDate(end_dt.year, end_dt.month, end_dt.day)) + else: + # Show end date field for other event types + end_date_label.setVisible(True) + end_date_field.setVisible(True) + def get_data(self): + start_date = self.start_date.date().toString("yyyy-MM-dd") + keyword = self.keyword.currentText() + + if keyword == "EZ pauschal": + # For EZ pauschals, calculate end date as start + 4 weeks + start_dt = datetime.fromisoformat(start_date) + end_dt = start_dt + relativedelta(years = 2, days = -1) + end_date = end_dt.strftime("%Y-%m-%d") + else: + end_date = self.end_date.date().toString("yyyy-MM-dd") + return start_date, end_date, keyword + + +class CalendarManagerGUI(QMainWindow): + def __init__(self): + super().__init__() + self.setWindowTitle("Ausfallzeitenrechner") + self.setMinimumSize(800, 600) + + # Initialize backend components + self.keyword_list = ["EZ 100%", "EZ 50%", "EZ pauschal", "Sonstige"] + self.calendar_manager = CalendarManager() + self.date_calculator = DateCalculator() + self.prediction_controller = PredictionController( + self.calendar_manager, + self.date_calculator, + self.keyword_list + ) + + self.init_ui() + self.create_menus() + + def create_menus(self): + # Create menu bar + menubar = self.menuBar() + + # File menu + file_menu = menubar.addMenu('File') + + # Save action + save_action = QAction('Save', self) + save_action.setShortcut('Ctrl+S') + save_action.triggered.connect(self.save_file) + file_menu.addAction(save_action) + + # Load action + load_action = QAction('Load', self) + load_action.setShortcut('Ctrl+O') + load_action.triggered.connect(self.load_file) + file_menu.addAction(load_action) + + # Clear action + clear_action = QAction('Clear', self) + clear_action.setShortcut('Ctrl+N') + clear_action.triggered.connect(self.clear_entries) + file_menu.addAction(clear_action) + + # Exit action + exit_action = QAction('Exit', self) + exit_action.setShortcut('Ctrl+Q') + exit_action.triggered.connect(self.close) + file_menu.addAction(exit_action) + + def init_ui(self): + central_widget = QWidget() + main_layout = QVBoxLayout() + + top_layout = QHBoxLayout() + + # Launch date input + launch_date_layout = QVBoxLayout() + launch_date_label = QLabel("Promotionsdatum:") + self.launch_date_edit = QDateEdit() + self.launch_date_edit.setCalendarPopup(True) # Enable calendar popup + self.launch_date_edit.setDate(QDate.currentDate()) + self.launch_date_edit.dateChanged.connect(self.update_prediction) # Auto-update on change + launch_date_layout.addWidget(launch_date_label) + launch_date_layout.addWidget(self.launch_date_edit) + top_layout.addLayout(launch_date_layout) + + # Duration input + duration_layout = QVBoxLayout() + duration_label = QLabel("Bewerbungszeitraum (Jahre):") + self.duration_spin = QSpinBox() + self.duration_spin.setRange(1, 99) + self.duration_spin.setValue(1) + self.duration_spin.valueChanged.connect(self.update_prediction) # Auto-update on change + duration_layout.addWidget(duration_label) + duration_layout.addWidget(self.duration_spin) + top_layout.addLayout(duration_layout) + + # Prediction result + prediction_result_layout = QVBoxLayout() + prediction_result_label = QLabel("Bewerbungsfrist:") + self.prediction_result = QDateEdit() + self.prediction_result.setReadOnly(True) + self.prediction_result.setButtonSymbols(QDateEdit.ButtonSymbols.NoButtons) + prediction_result_layout.addWidget(prediction_result_label) + prediction_result_layout.addWidget(self.prediction_result) + + top_layout.addLayout(prediction_result_layout) + + main_layout.addLayout(top_layout) + + # Events section + events_layout = QVBoxLayout() + events_title = QLabel("<h3>Ausfallzeiten</h3>") + events_layout.addWidget(events_title) + + # Add event button + add_event_button = QPushButton("Eintrag hinzufügen") + add_event_button.clicked.connect(self.add_event) + events_layout.addWidget(add_event_button) + + # Events table + self.events_table = QTableWidget() + self.events_table.setColumnCount(7) # ID (hidden), Start, End, Keyword, CorrectedStart, CorrectedEnd, Actions + self.events_table.setHorizontalHeaderLabels(["ID", "Anfangsdatum", "Enddatum", "Art", "Korr. Start", "Korr. Ende", "Aktionen"]) + self.events_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) + self.events_table.setColumnHidden(0, True) # Hide ID column + events_layout.addWidget(self.events_table) + + main_layout.addLayout(events_layout) + + # Set main layout + central_widget.setLayout(main_layout) + self.setCentralWidget(central_widget) + + # Load initial data + self.update_events_table() + self.update_prediction() # Calculate initial prediction + + def update_prediction(self): + """Update prediction whenever needed""" + try: + launch_date = self.launch_date_edit.date().toString("yyyy-MM-dd") + duration_years = self.duration_spin.value() + + self.prediction_controller.make_prediction(launch_date, duration_years) + prediction_date = self.prediction_controller.get_prediction() + + if prediction_date: + self.prediction_result.setDate(prediction_date) + + # Update table to show corrected dates + self.update_events_table() + + except Exception as e: + self.prediction_result.setText("Error in calculation") + print(f"Error calculating prediction: {str(e)}") + + def add_event(self): + dialog = AddEventDialog(self.keyword_list, self) + if dialog.exec_(): + start_date, end_date, keyword = dialog.get_data() + try: + self.calendar_manager.add_entry(start_date, end_date, keyword) + self.update_events_table() + self.update_prediction() # Auto-update prediction + except Exception as e: + QMessageBox.critical(self, "Error", f"Error adding event: {str(e)}") + + def modify_event(self, event_id): + entry = self.calendar_manager.get_entry_by_id(event_id) + if entry: + dialog = ModifyEventDialog(entry, self.keyword_list, self) + if dialog.exec_(): + start_date, end_date, keyword = dialog.get_data() + try: + self.calendar_manager.modify_entry(event_id, + datetime.fromisoformat(start_date), + datetime.fromisoformat(end_date), + keyword) + self.update_events_table() + self.update_prediction() # Auto-update prediction + except Exception as e: + QMessageBox.critical(self, "Error", f"Error modifying event: {str(e)}") + + def delete_event(self, event_id): + reply = QMessageBox.question(self, "Eintrag löschen", + "Wollen Sie diesen Eintrag löschen?", + QMessageBox.Yes | QMessageBox.No) + if reply == QMessageBox.Yes: + try: + self.calendar_manager.delete_entry(event_id) + self.update_events_table() + self.update_prediction() # Auto-update prediction + except Exception as e: + QMessageBox.critical(self, "Error", f"Error deleting event: {str(e)}") + + def update_events_table(self): + # Clear table + self.events_table.setRowCount(0) + + # Add entries to table + entries = self.calendar_manager.list_entries() + for i, entry in enumerate(entries): + self.events_table.insertRow(i) + + # Set item data + self.events_table.setItem(i, 0, QTableWidgetItem(entry.id)) + self.events_table.setItem(i, 1, QTableWidgetItem(entry.start_date.strftime("%Y-%m-%d"))) + self.events_table.setItem(i, 2, QTableWidgetItem(entry.end_date.strftime("%Y-%m-%d"))) + self.events_table.setItem(i, 3, QTableWidgetItem(entry.keyword)) + + # Corrected dates + corrected_start = entry.corrected_start_date.strftime("%Y-%m-%d") if entry.corrected_start_date else "" + corrected_end = entry.corrected_end_date.strftime("%Y-%m-%d") if entry.corrected_end_date else "" + self.events_table.setItem(i, 4, QTableWidgetItem(corrected_start)) + self.events_table.setItem(i, 5, QTableWidgetItem(corrected_end)) + + # Action buttons + actions_widget = QWidget() + actions_layout = QHBoxLayout() + actions_layout.setContentsMargins(0, 0, 0, 0) + + modify_button = QPushButton("Editieren") + delete_button = QPushButton("Löschen") + + # Use lambda with default argument to capture the correct event_id + modify_button.clicked.connect(lambda checked, eid=entry.id: self.modify_event(eid)) + delete_button.clicked.connect(lambda checked, eid=entry.id: self.delete_event(eid)) + + actions_layout.addWidget(modify_button) + actions_layout.addWidget(delete_button) + actions_widget.setLayout(actions_layout) + + self.events_table.setCellWidget(i, 6, actions_widget) + + def save_file(self): + """Save calendar entries to a JSON file""" + # if not self.calendar_manager.filename: + file_path, _ = QFileDialog.getSaveFileName(self, "Einträge speichern", "", "JSON Files (*.json)") + if not file_path: + return + self.calendar_manager.switch_file(file_path) + + try: + self.calendar_manager.save_entries() + QMessageBox.information(self, "Speichern erfolgreich", f"Einträge gespeichert in {self.calendar_manager.filename}") + except Exception as e: + QMessageBox.critical(self, "Error", f"Failed to save calendar: {str(e)}") + + def load_file(self): + """Load calendar entries from a JSON file""" + file_path, _ = QFileDialog.getOpenFileName(self, "Einträge laden", "", "JSON Files (*.json)") + if not file_path: + return + + try: + self.calendar_manager.load_file(file_path) + self.update_events_table() + self.update_prediction() # Auto-update prediction + QMessageBox.information(self, "Laden erfolgreich", f"Einträge erfolgreich von {file_path} geladen") + except Exception as e: + QMessageBox.critical(self, "Error", f"Failed to load calendar: {str(e)}") + + def clear_entries(self): + """Clear all calendar entries""" + reply = QMessageBox.question(self, "Einträge löschen", + "Alle Einträge löschen?", + QMessageBox.Yes | QMessageBox.No) + if reply == QMessageBox.Yes: + try: + self.calendar_manager.clear_entries() + self.update_events_table() + self.update_prediction() # Auto-update prediction + QMessageBox.information(self, "Erfolg", "Alle Einträge gelöscht!") + except Exception as e: + QMessageBox.critical(self, "Error", f"Failed to clear calendar: {str(e)}") + + +def main(): + app = QApplication(sys.argv) + app.setStyle('Fusion') + window = CalendarManagerGUI() + window.show() + sys.exit(app.exec_()) + + +if __name__ == "__main__": + main()
\ No newline at end of file |
