diff --git a/absencedialog.cpp b/absencedialog.cpp new file mode 100644 index 0000000..40c66fa --- /dev/null +++ b/absencedialog.cpp @@ -0,0 +1,84 @@ +#include "absencedialog.h" +#include "ui_absencedialog.h" + +#include +#include +#include +#include +#include +#include + +#include "absencesmodel.h" + +AbsenceDialog::AbsenceDialog(int userId, const QDate &date, ZeiterfassungApi &erfassung, QWidget *parent) : + ZeiterfassungDialog(parent), + ui(new Ui::AbsenceDialog) +{ + ui->setupUi(this); + + ui->labelTitle->setText(tr("Absences for %0").arg(QLocale().toString(date))); + + m_model = new AbsencesModel(userId, date, erfassung, this); + connect(m_model, &AbsencesModel::errorOccured, this, &AbsenceDialog::errorOccured); + + ui->treeView->setModel(m_model); + ui->treeView->setEnabled(m_model->enabled()); + connect(m_model, &AbsencesModel::enabledChanged, ui->treeView, &QWidget::setEnabled); + + connect(ui->treeView, &QWidget::customContextMenuRequested, this, &AbsenceDialog::customContextMenuRequested); +} + +AbsenceDialog::~AbsenceDialog() +{ + delete ui; +} + +void AbsenceDialog::errorOccured(const QString &message) +{ + QMessageBox::warning(this, tr("Could not load absences!"), tr("Could not load absences!") % "\n\n" % message); +} + +void AbsenceDialog::customContextMenuRequested(const QPoint &pos) +{ + auto index = ui->treeView->indexAt(pos); + + if(!index.isValid()) + { + QMenu menu; + auto createAction = menu.addAction(tr("Create absence")); + auto refreshAction = menu.addAction(QIcon(QPixmap(QStringLiteral(":/zeiterfassungguilib/images/refresh.png"))), tr("Refresh absences")); + auto selectedAction = menu.exec(ui->treeView->viewport()->mapToGlobal(pos)); + if(selectedAction == createAction) + { + //TODO + } + else if(selectedAction == refreshAction) + { + m_model->refresh(); + } + } + else + { + auto absence = m_model->absences().at(index.row()); + + QMenu menu; + auto editAction = menu.addAction(tr("Edit absence")); + auto deleteAction = menu.addAction(tr("Delete absence")); + auto selectedAction = menu.exec(ui->treeView->viewport()->mapToGlobal(pos)); + if(selectedAction == editAction) + { + //TODO + } + else if(selectedAction == deleteAction) + { + QMessageBox msgBox; + msgBox.setText(tr("Do you really want to delete the absence?")); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel); + msgBox.setDefaultButton(QMessageBox::Cancel); + if(msgBox.exec() == QMessageBox::Yes) + { + //TODO + } + } + } +} diff --git a/absencedialog.h b/absencedialog.h new file mode 100644 index 0000000..9ab7e80 --- /dev/null +++ b/absencedialog.h @@ -0,0 +1,28 @@ +#pragma once + +#include "zeiterfassungdialog.h" + +class QDate; + +class ZeiterfassungApi; + +class AbsencesModel; +namespace Ui { class AbsenceDialog; } + +class AbsenceDialog : public ZeiterfassungDialog +{ + Q_OBJECT + +public: + explicit AbsenceDialog(int userId, const QDate &date, ZeiterfassungApi &erfassung, QWidget *parent = 0); + ~AbsenceDialog(); + +private Q_SLOTS: + void errorOccured(const QString &message); + void customContextMenuRequested(const QPoint &pos); + +private: + Ui::AbsenceDialog *ui; + + AbsencesModel *m_model; +}; diff --git a/absencedialog.ui b/absencedialog.ui new file mode 100644 index 0000000..4db3095 --- /dev/null +++ b/absencedialog.ui @@ -0,0 +1,83 @@ + + + AbsenceDialog + + + + 0 + 0 + 947 + 300 + + + + Absences + + + + + + + 16 + + + + Absences for + + + + + + + Qt::CustomContextMenu + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + + buttonBox + accepted() + AbsenceDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + AbsenceDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/absenceplugin.cpp b/absenceplugin.cpp new file mode 100644 index 0000000..2e09928 --- /dev/null +++ b/absenceplugin.cpp @@ -0,0 +1,38 @@ +#include "absenceplugin.h" + +#include +#include +#include +#include +#include + +#include "mainwindow.h" +#include "stripswidget.h" + +#include "absencewidget.h" + +AbsencePlugin::AbsencePlugin(QObject *parent) : + ZeiterfassungPlugin(parent) +{ + qDebug() << "called"; + + static auto dir = QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(QStringLiteral("translations")); + + if(m_translator.load(QLocale(), QStringLiteral("absenceplugin"), QStringLiteral("_"), dir)) + { + if(!QCoreApplication::installTranslator(&m_translator)) + { + qWarning() << "could not install translation absenceplugin"; + } + } + else + { + qWarning() << "could not load translation absenceplugin"; + } +} + +void AbsencePlugin::attachTo(MainWindow &mainWindow) +{ + for(auto stripsWidget : mainWindow.stripsWidgets()) + stripsWidget->headerLayout()->addWidget(new AbsenceWidget(*stripsWidget)); +} diff --git a/absenceplugin.h b/absenceplugin.h new file mode 100644 index 0000000..b3884b8 --- /dev/null +++ b/absenceplugin.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +#include "zeiterfassungplugin.h" + +class MainWindow; + +class Q_DECL_EXPORT AbsencePlugin : public ZeiterfassungPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "dbsoftware.zeiterfassung.plugin/1.0" FILE "absenceplugin.json") + Q_INTERFACES(ZeiterfassungPlugin) + +public: + explicit AbsencePlugin(QObject *parent = Q_NULLPTR); + + // ZeiterfassungPlugin interface + void attachTo(MainWindow &mainWindow) Q_DECL_OVERRIDE; + +private: + QTranslator m_translator; +}; diff --git a/absenceplugin.json b/absenceplugin.json new file mode 100644 index 0000000..e69de29 diff --git a/absenceplugin.pro b/absenceplugin.pro new file mode 100644 index 0000000..dac0298 --- /dev/null +++ b/absenceplugin.pro @@ -0,0 +1,26 @@ +QT += core network gui widgets + +DBLIBS += zeiterfassungcore zeiterfassunggui + +TARGET = absenceplugin + +HEADERS += absencedialog.h \ + absencesmodel.h \ + absenceplugin.h \ + absencewidget.h + +SOURCES += absencedialog.cpp \ + absencesmodel.cpp \ + absenceplugin.cpp \ + absencewidget.cpp + +FORMS += absencedialog.ui + +RESOURCES += absenceplugin_resources.qrc + +TRANSLATIONS += translations/absenceplugin_en.ts \ + translations/absenceplugin_de.ts + +OTHER_FILES += absenceplugin.json + +include(../plugin.pri) diff --git a/absenceplugin_resources.qrc b/absenceplugin_resources.qrc new file mode 100644 index 0000000..a3b089d --- /dev/null +++ b/absenceplugin_resources.qrc @@ -0,0 +1,5 @@ + + + images/absence.png + + diff --git a/absencesmodel.cpp b/absencesmodel.cpp new file mode 100644 index 0000000..81ac369 --- /dev/null +++ b/absencesmodel.cpp @@ -0,0 +1,123 @@ +#include "absencesmodel.h" + +#include "zeiterfassungapi.h" + +AbsencesModel::AbsencesModel(int userId, const QDate &date, ZeiterfassungApi &erfassung, QObject *parent) : + QAbstractListModel(parent), + m_userId(userId), + m_erfassung(erfassung) +{ + connect(this, &AbsencesModel::dateChanged, this, &AbsencesModel::refresh); + + setDate(date); +} + +bool AbsencesModel::enabled() const +{ + return m_reply == Q_NULLPTR; +} + +const QDate &AbsencesModel::date() const +{ + return m_date; +} + +const QVector &AbsencesModel::absences() const +{ + return m_absences; +} + +int AbsencesModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + + return m_absences.count(); +} + +int AbsencesModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + + return 5; +} + +QVariant AbsencesModel::data(const QModelIndex &index, int role) const +{ + Q_ASSERT(index.row() < m_absences.count()); + const auto &absence = m_absences.at(index.row()); + + switch(role) + { + case Qt::DisplayRole: + case Qt::EditRole: + switch(index.column()) + { + case 0: return absence.compositeId; + case 1: return absence.start; + case 2: return absence.end; + case 3: return absence.hourCategory; + case 4: return absence.text; + } + } + + return QVariant(); +} + +QVariant AbsencesModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + switch(orientation) + { + case Qt::Horizontal: + switch(role) + { + case Qt::DisplayRole: + case Qt::EditRole: + switch(section) + { + case 0: return tr("Id"); + case 1: return tr("Start"); + case 2: return tr("End"); + case 3: return tr("Hour Category"); + case 4: return tr("Text"); + } + } + default: + qt_noop(); + } + + return QVariant(); +} + +void AbsencesModel::refresh() +{ + auto oldEnabled = enabled(); + + m_reply = m_erfassung.doGetAbsences(m_userId, m_date, m_date); + connect(m_reply.get(), &ZeiterfassungReply::finished, this, &AbsencesModel::finished); + + if(oldEnabled != enabled()) + Q_EMIT enabledChanged(enabled()); +} + +void AbsencesModel::setDate(const QDate &date) +{ + if(m_date != date) + Q_EMIT dateChanged(m_date = date); +} + +void AbsencesModel::finished() +{ + if(!m_reply->success()) + Q_EMIT errorOccured(m_reply->message()); + + beginResetModel(); + m_absences = m_reply->absences(); + endResetModel(); + + auto oldEnabled = enabled(); + + m_reply = Q_NULLPTR; + + if(oldEnabled != enabled()) + Q_EMIT enabledChanged(enabled()); +} diff --git a/absencesmodel.h b/absencesmodel.h new file mode 100644 index 0000000..b183f69 --- /dev/null +++ b/absencesmodel.h @@ -0,0 +1,51 @@ +#pragma once + +#include + +#include +#include + +#include "replies/getabsencesreply.h" + +class ZeiterfassungApi; + +class AbsencesModel : public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(bool enabled READ enabled NOTIFY enabledChanged) + Q_PROPERTY(QDate date READ date WRITE setDate NOTIFY dateChanged) + +public: + explicit AbsencesModel(int userId, const QDate &date, ZeiterfassungApi &erfassung, QObject *parent = Q_NULLPTR); + + bool enabled() const; + + const QDate &date() const; + + const QVector &absences() const; + + // QAbstractItemModel interface + int rowCount(const QModelIndex &parent) const Q_DECL_OVERRIDE; + int columnCount(const QModelIndex &parent) const Q_DECL_OVERRIDE; + QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE; + QVariant headerData(int section, Qt::Orientation orientation, int role) const Q_DECL_OVERRIDE; + +Q_SIGNALS: + void enabledChanged(bool enabled); + void dateChanged(const QDate &date); + void errorOccured(const QString &message); + +public Q_SLOTS: + void refresh(); + void setDate(const QDate &date); + +private Q_SLOTS: + void finished(); + +private: + int m_userId; + QDate m_date; + ZeiterfassungApi &m_erfassung; + std::unique_ptr m_reply; + QVector m_absences; +}; diff --git a/absencewidget.cpp b/absencewidget.cpp new file mode 100644 index 0000000..444f87f --- /dev/null +++ b/absencewidget.cpp @@ -0,0 +1,23 @@ +#include "absencewidget.h" + +#include "stripswidget.h" +#include "mainwindow.h" + +#include "absencedialog.h" + +AbsenceWidget::AbsenceWidget(StripsWidget &stripsWidget) : + QToolButton(&stripsWidget), + m_stripsWidget(stripsWidget) +{ + setIcon(QIcon(QStringLiteral(":/zeiterfassung/plugins/absenceplugin/images/absence.png"))); + setText(tr("Absence")); + + connect(this, &QAbstractButton::pressed, this, &AbsenceWidget::pressedSlot); +} + +void AbsenceWidget::pressedSlot() +{ + AbsenceDialog dialog(m_stripsWidget.mainWindow().userInfo().userId, m_stripsWidget.date(), + m_stripsWidget.mainWindow().erfassung(), this); + dialog.exec(); +} diff --git a/absencewidget.h b/absencewidget.h new file mode 100644 index 0000000..a93cc34 --- /dev/null +++ b/absencewidget.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +class StripsWidget; + +class AbsenceWidget : public QToolButton +{ + Q_OBJECT + +public: + explicit AbsenceWidget(StripsWidget &stripsWidget); + +private Q_SLOTS: + void pressedSlot(); + +private: + StripsWidget &m_stripsWidget; +}; diff --git a/images/absence.png b/images/absence.png new file mode 100644 index 0000000..4a33f1c Binary files /dev/null and b/images/absence.png differ diff --git a/translations/absenceplugin_de.ts b/translations/absenceplugin_de.ts new file mode 100644 index 0000000..131891c --- /dev/null +++ b/translations/absenceplugin_de.ts @@ -0,0 +1,88 @@ + + + + + AbsenceDialog + + + Absences + Abwesenheiten + + + + Absences for %0 + Abwesenheiten für %0 + + + + dd.MM.yyyy + dd.MM.yyyy + + + + Could not load absences! + Konnte Abwesenheiten nicht laden! + + + + Create absence + Abwesenheit erstellen + + + + Refresh absences + Abwesenheiten aktualisieren + + + + Edit absence + Abwesenheit bearbeiten + + + + Delete absence + Abwesenheit löschen + + + + Do you really want to delete the absence? + Möchten Sie die Abwesenheit wirklich löschen? + + + + AbsenceWidget + + + Absence + Abwesenheit + + + + AbsencesModel + + + Id + Id + + + + Start + Start + + + + End + Ende + + + + Hour Category + Stunden Kategorie + + + + Text + Text + + + diff --git a/translations/absenceplugin_en.ts b/translations/absenceplugin_en.ts new file mode 100644 index 0000000..0794cbf --- /dev/null +++ b/translations/absenceplugin_en.ts @@ -0,0 +1,88 @@ + + + + + AbsenceDialog + + + Absences + + + + + Absences for %0 + + + + + dd.MM.yyyy + + + + + Could not load absences! + + + + + Create absence + + + + + Refresh absences + + + + + Edit absence + + + + + Delete absence + + + + + Do you really want to delete the absence? + + + + + AbsenceWidget + + + Absence + + + + + AbsencesModel + + + Id + + + + + Start + + + + + End + + + + + Hour Category + + + + + Text + + + +