diff --git a/README.md b/README.md index f771513..fc95ffe 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,73 @@ -# Arbeitszeitrechner +
->**Du hast Gleitzeit und möchtest wissen, wann du ohne Minusstunden nach Hause kannst? Dann bist du hier genau richtig!** -*** ->![Image](./images/screen.png "Screenshot") +# 🕐 Arbeitszeitrechner +**Du hast Gleitzeit und möchtest wissen, wann du ohne Minusstunden nach Hause kannst?** +Dann bist du hier genau richtig. +![screenshot](./images/screen.png) +
+ +--- + +## ✨ Features + +- **Netto-Arbeitszeit berechnen** — Einstempelzeit + beliebig viele Pausen → exakte Netto-Zeit +- **Zeitkonto** — positiver oder negativer Saldo gegenüber der Regelarbeitszeit auf einen Blick +- **Ausstempelzeit vorschlagen** — kein Endzeitpunkt? Die App berechnet ihn automatisch +- **Mitternachts-Überlauf** — Schichten die über 00:00 gehen werden korrekt behandelt +- **ArbZG-Hinweise** — Erinnerung an gesetzliche Mindestpausen (§ 4 ArbZG) bei langen Arbeitstagen +- **Einstellungen werden gespeichert** — Regelarbeitszeit bleibt nach dem Neustart erhalten +- **Dark Mode** — Fusion-Palette, augenschonend auch spät abends + +--- + +## 🔧 Voraussetzungen + +| Abhängigkeit | Mindestversion | +|---|---| +| Qt | 6.x (`Qt6::Widgets`) | +| CMake | 3.16 | +| C++-Compiler | C++17 (GCC, Clang, MSVC) | + +**Arch Linux:** +```bash +sudo pacman -S --needed qt6-base cmake gcc make +``` + +**Ubuntu / Debian:** +```bash +sudo apt install qt6-base-dev cmake g++ +``` + +--- + +## 🚀 Build & Start + +```bash +# Konfigurieren +cmake -B build -DCMAKE_BUILD_TYPE=Release + +# Kompilieren +cmake --build build -j$(nproc) + +# Starten +./build/ArbeitszeitRechner +``` + +--- + +## 📖 Benutzung + +1. **Einstempelzeit** eintragen +2. **Pausen** einzeln in Minuten hinzufügen (Entf-Taste entfernt markierte Einträge) +3. Optional: **Ausstempelzeit** aktivieren, um die tatsächlich geleistete Zeit zu berechnen +4. **Regelarbeitszeit** (Std : Min) einstellen — wird beim nächsten Start gespeichert +5. **„Berechnung starten"** → Netto-Zeit, Zeitkonto und ggf. Ausstempel-Vorschlag erscheinen + +--- + +## 📄 Lizenz + +MIT — mach damit was du willst. diff --git a/WorkTimeCalculator.cpp b/WorkTimeCalculator.cpp index eead62a..c568ffc 100644 --- a/WorkTimeCalculator.cpp +++ b/WorkTimeCalculator.cpp @@ -6,16 +6,25 @@ #include #include #include +#include +#include +#include + +WorkTimeCalculator::WorkTimeCalculator(QWidget *parent) +: QMainWindow(parent) { + // QSettings braucht Organisation + Anwendung. Einmal global setzen, + // dann reicht QSettings() ohne Argumente überall in der App. + QCoreApplication::setOrganizationName("DietzLabs"); + QCoreApplication::setApplicationName("ArbeitszeitRechner"); -WorkTimeCalculator::WorkTimeCalculator() { // Hauptlayout QWidget *mainWidget = new QWidget; QVBoxLayout *layout = new QVBoxLayout(mainWidget); - mainWidget->setLayout(layout); // Setze das Layout für das Widget // Einstempelzeit QLabel *startTimeLabel = new QLabel("Einstempelzeit:"); startTimeEdit = new QTimeEdit; + startTimeEdit->setDisplayFormat("hh:mm"); startTimeEdit->setTime(QTime::currentTime()); layout->addWidget(startTimeLabel); layout->addWidget(startTimeEdit); @@ -24,17 +33,26 @@ WorkTimeCalculator::WorkTimeCalculator() { QLabel *breaksLabel = new QLabel("Pausenzeiten (Minuten):"); layout->addWidget(breaksLabel); - addBreakButton = new QPushButton("Pause hinzufügen"); - layout->addWidget(addBreakButton); + QHBoxLayout *breakButtonLayout = new QHBoxLayout(); + addBreakButton = new QPushButton("Pause hinzufügen"); + removeBreakButton = new QPushButton("Pause entfernen"); + breakButtonLayout->addWidget(addBreakButton); + breakButtonLayout->addWidget(removeBreakButton); + layout->addLayout(breakButtonLayout); breakList = new QListWidget; layout->addWidget(breakList); - connect(addBreakButton, &QPushButton::clicked, this, &WorkTimeCalculator::addBreak); + connect(addBreakButton, &QPushButton::clicked, this, &WorkTimeCalculator::addBreak); + connect(removeBreakButton, &QPushButton::clicked, this, &WorkTimeCalculator::removeSelectedBreak); + // Komfort: Entf-Taste löscht die markierte Pause. + QShortcut *del = new QShortcut(QKeySequence(Qt::Key_Delete), breakList); + connect(del, &QShortcut::activated, this, &WorkTimeCalculator::removeSelectedBreak); // Ausstempelzeit (optional) includeEndTimeCheckbox = new QCheckBox("Ausstempelzeit angeben"); endTimeEdit = new QTimeEdit; + endTimeEdit->setDisplayFormat("hh:mm"); endTimeEdit->setTime(QTime::currentTime()); endTimeEdit->setVisible(false); layout->addWidget(includeEndTimeCheckbox); @@ -61,6 +79,7 @@ WorkTimeCalculator::WorkTimeCalculator() { layout->addWidget(calculateButton); resultLabel = new QLabel; + resultLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); layout->addWidget(resultLabel); connect(calculateButton, &QPushButton::clicked, this, &WorkTimeCalculator::calculateWorkTime); @@ -75,7 +94,6 @@ WorkTimeCalculator::WorkTimeCalculator() { darkPalette.setColor(QPalette::Text, Qt::white); darkPalette.setColor(QPalette::Button, QColor(53, 53, 53)); darkPalette.setColor(QPalette::ButtonText, Qt::white); - qApp->setPalette(darkPalette); setCentralWidget(mainWidget); @@ -88,60 +106,127 @@ WorkTimeCalculator::~WorkTimeCalculator() { saveSettings(); } -void WorkTimeCalculator::addBreak() { - bool ok; - int breakTime = QInputDialog::getInt(this, "Pause hinzufügen", "Pause (Minuten):", 0, 0, 1440, 1, &ok); - if (ok && breakTime > 0) { - breakList->addItem(QString::number(breakTime) + " Minuten"); - } +// --- Pausen ----------------------------------------------------------------- + +void WorkTimeCalculator::addBreakItem(int minutes) { + auto *item = new QListWidgetItem(QString("%1 Minuten").arg(minutes)); + item->setData(BreakRole, minutes); // echte Daten getrennt vom Anzeigetext + breakList->addItem(item); } -void WorkTimeCalculator::calculateWorkTime() { - QTime startTime = startTimeEdit->time(); - int totalBreakMinutes = 0; +void WorkTimeCalculator::addBreak() { + bool ok = false; + int breakTime = QInputDialog::getInt( + this, "Pause hinzufügen", "Pause (Minuten):", + /*value*/ 30, /*min*/ 1, /*max*/ 1440, /*step*/ 1, &ok); + if (ok) + addBreakItem(breakTime); +} - for (int i = 0; i < breakList->count(); ++i) { - totalBreakMinutes += breakList->item(i)->text().split(" ")[0].toInt(); +void WorkTimeCalculator::removeSelectedBreak() { + qDeleteAll(breakList->selectedItems()); // entfernt + löscht die markierten Items +} + +int WorkTimeCalculator::totalBreakMinutes() const { + int total = 0; + for (int i = 0; i < breakList->count(); ++i) + total += breakList->item(i)->data(BreakRole).toInt(); + return total; +} + +// --- Hilfsfunktionen -------------------------------------------------------- + +int WorkTimeCalculator::minutesBetween(const QTime &start, const QTime &end, bool *crossesMidnight) { + int secs = start.secsTo(end); + bool wrapped = false; + if (secs < 0) { // Ende liegt am Folgetag (z. B. Spät-/Nachtschicht) + secs += 24 * 3600; + wrapped = true; } + if (crossesMidnight) + *crossesMidnight = wrapped; + return secs / 60; +} - // Gesamte Pausenzeit anzeigen - QString breakTimeMessage = QString("Gesamte Pausenzeit: %1 Minuten").arg(totalBreakMinutes); +QString WorkTimeCalculator::formatDuration(int minutes) { + int a = qAbs(minutes); + return QString("%1:%2").arg(a / 60).arg(a % 60, 2, 10, QChar('0')); +} + +QString WorkTimeCalculator::formatSignedDuration(int minutes) { + const QString sign = minutes < 0 ? "-" : "+"; + return sign + formatDuration(minutes); +} + +// --- Berechnung ------------------------------------------------------------- + +void WorkTimeCalculator::calculateWorkTime() { + const QTime startTime = startTimeEdit->time(); + const int breaks = totalBreakMinutes(); + const int dailyMinutes = dailyHoursSpin->value() * 60 + dailyMinutesSpin->value(); QTime endTime; + bool endIsNextDay = false; + if (includeEndTimeCheckbox->isChecked()) { endTime = endTimeEdit->time(); } else { - // Ausstempelzeit berechnen, wenn nicht angegeben - int dailyMinutes = dailyHoursSpin->value() * 60 + dailyMinutesSpin->value(); - endTime = startTime.addSecs((dailyMinutes + totalBreakMinutes) * 60); + // Vorschlag: Anwesenheit = Soll-Netto + Pausen. + const int presenceMinutes = dailyMinutes + breaks; + endTime = startTime.addSecs(presenceMinutes * 60); + endIsNextDay = (startTime.secsTo(endTime) < 0) || (presenceMinutes >= 24 * 60); } - int workedMinutes = startTime.secsTo(endTime) / 60 - totalBreakMinutes; + // Vergangene Anwesenheit inkl. korrekter Mitternachts-Behandlung. + bool crossesMidnight = false; + const int presenceMinutes = minutesBetween(startTime, endTime, &crossesMidnight); + if (includeEndTimeCheckbox->isChecked()) + endIsNextDay = crossesMidnight; - int dailyMinutes = dailyHoursSpin->value() * 60 + dailyMinutesSpin->value(); - int balanceMinutes = workedMinutes - dailyMinutes; + const int workedMinutes = presenceMinutes - breaks; - QString result = QString("Netto-Arbeitszeit: %1 Stunden %2 Minuten\n") - .arg(workedMinutes / 60) - .arg(workedMinutes % 60); - result += QString("Zeitkonto: %1 Minuten\n").arg(balanceMinutes); - result += breakTimeMessage; // Gesamte Pausenzeit hinzufügen + // Plausibilitätsprüfung im manuellen Modus. + if (includeEndTimeCheckbox->isChecked() && workedMinutes < 0) { + QMessageBox::warning(this, "Ungültige Eingabe", + "Die Pausenzeit ist länger als die Anwesenheit. " + "Bitte Ein-/Ausstempelzeit und Pausen prüfen."); + return; + } + + const int balanceMinutes = workedMinutes - dailyMinutes; + + QString result; + result += QString("Netto-Arbeitszeit: %1 Std\n").arg(formatDuration(workedMinutes)); + result += QString("Zeitkonto: %1 Std\n").arg(formatSignedDuration(balanceMinutes)); + result += QString("Gesamte Pausenzeit: %1 Minuten").arg(breaks); if (!includeEndTimeCheckbox->isChecked()) { - result += QString("\nVorgeschlagene Ausstempelzeit: %1").arg(endTime.toString("hh:mm")); + result += QString("\nVorgeschlagene Ausstempelzeit: %1%2") + .arg(endTime.toString("hh:mm"), + endIsNextDay ? " (Folgetag)" : ""); + } else if (endIsNextDay) { + result += "\nHinweis: Ausstempelzeit liegt am Folgetag (über Mitternacht)."; } + // Hinweis auf gesetzliche Mindestpausen nach ArbZG §4 (kein Rechtsrat). + if (workedMinutes > 9 * 60 && breaks < 45) + result += "\n\n⚠ Ab 9 Std Arbeitszeit sind mind. 45 Minuten Pause vorgesehen (ArbZG §4)."; + else if (workedMinutes > 6 * 60 && breaks < 30) + result += "\n\n⚠ Ab 6 Std Arbeitszeit sind mind. 30 Minuten Pause vorgesehen (ArbZG §4)."; + resultLabel->setText(result); } +// --- Persistenz ------------------------------------------------------------- + void WorkTimeCalculator::loadSettings() { - QSettings settings("ArbeitszeitRechner", "Settings"); + QSettings settings; // nutzt Organisation/Anwendung aus dem Konstruktor dailyHoursSpin->setValue(settings.value("dailyHours", 8).toInt()); dailyMinutesSpin->setValue(settings.value("dailyMinutes", 0).toInt()); } void WorkTimeCalculator::saveSettings() { - QSettings settings("ArbeitszeitRechner", "Settings"); + QSettings settings; settings.setValue("dailyHours", dailyHoursSpin->value()); settings.setValue("dailyMinutes", dailyMinutesSpin->value()); } diff --git a/WorkTimeCalculator.h b/WorkTimeCalculator.h index 8e5366f..9f41a0d 100644 --- a/WorkTimeCalculator.h +++ b/WorkTimeCalculator.h @@ -8,35 +8,48 @@ #include // Widget zur Anzeige der Pausenzeiten #include // Eingabe für numerische Werte (z. B. Stunden/Minuten) #include // Widget zur Anzeige von Text +#include class WorkTimeCalculator : public QMainWindow { Q_OBJECT // Erforderlich für Qt-Signal- und Slot-Mechanismus public: // Konstruktor und Destruktor - WorkTimeCalculator(); // Initialisiert das GUI und die Logik - ~WorkTimeCalculator(); // Speichert Einstellungen beim Schließen + explicit WorkTimeCalculator(QWidget *parent = nullptr); // Initialisiert GUI und Logik + ~WorkTimeCalculator() override; // Speichert Einstellungen beim Schließen private slots: // Slots für Benutzeraktionen - void addBreak(); // Hinzufügen einer neuen Pause + void addBreak(); // Hinzufügen einer neuen Pause + void removeSelectedBreak(); // Entfernt die markierte Pause void calculateWorkTime(); // Berechnet die Netto-Arbeitszeit private: // Hilfsfunktionen void loadSettings(); // Lädt gespeicherte Einstellungen void saveSettings(); // Speichert aktuelle Einstellungen + int totalBreakMinutes() const; // Summe aller Pausen (aus den Item-Daten) + void addBreakItem(int minutes); // Fügt eine Pause als Item mit Datenrolle ein + + // Liefert vergangene Minuten zwischen zwei Zeiten und behandelt + // den Überlauf über Mitternacht korrekt (Ende < Start -> +24h). + static int minutesBetween(const QTime &start, const QTime &end, bool *crossesMidnight = nullptr); + static QString formatDuration(int minutes); // "8:05" (Betrag, ohne Vorzeichen) + static QString formatSignedDuration(int minutes); // "+1:15" / "-0:45" // GUI-Komponenten - QTimeEdit *startTimeEdit; // Widget für die Einstempelzeit - QTimeEdit *endTimeEdit; // Widget für die Ausstempelzeit (optional) - QPushButton *addBreakButton; // Button, um eine Pause hinzuzufügen - QPushButton *calculateButton; // Button, um die Arbeitszeit zu berechnen - QCheckBox *includeEndTimeCheckbox; // Checkbox zur Aktivierung der Ausstempelzeit - QListWidget *breakList; // Liste der eingegebenen Pausenzeiten - QSpinBox *dailyHoursSpin; // Eingabe für tägliche Sollarbeitszeit (Stunden) - QSpinBox *dailyMinutesSpin; // Eingabe für tägliche Sollarbeitszeit (Minuten) - QLabel *resultLabel; // Anzeige für das Ergebnis der Berechnung + QTimeEdit *startTimeEdit; // Widget für die Einstempelzeit + QTimeEdit *endTimeEdit; // Widget für die Ausstempelzeit (optional) + QPushButton *addBreakButton; // Button, um eine Pause hinzuzufügen + QPushButton *removeBreakButton; // Button, um eine Pause zu entfernen + QPushButton *calculateButton; // Button, um die Arbeitszeit zu berechnen + QCheckBox *includeEndTimeCheckbox; // Checkbox zur Aktivierung der Ausstempelzeit + QListWidget *breakList; // Liste der eingegebenen Pausenzeiten + QSpinBox *dailyHoursSpin; // Eingabe für tägliche Sollarbeitszeit (Stunden) + QSpinBox *dailyMinutesSpin; // Eingabe für tägliche Sollarbeitszeit (Minuten) + QLabel *resultLabel; // Anzeige für das Ergebnis der Berechnung + + static constexpr int BreakRole = Qt::UserRole; // Datenrolle für die Pausenminuten }; #endif // WORKTIMECALCULATOR_H