- `kareer --db <path>` works for the GUI and every subcommand, before or after the subcommand name, and creates the file if missing. main() strips it from argv before CLI/GUI routing; every parser declares it so it shows in --help. - Path precedence: --db > KAREER_DB_PATH > the location chosen in the GUI (kareerrc [Database] Path, read at startup for GUI and CLI alike) > $XDG_DATA_HOME/kareer/kareer.sqlite. - First GUI run (nothing forced, no file yet, or the configured file has gone missing): DatabaseSetupDialog offers the default location, a folder of the user's choice, or an existing database used in place. Until then JobsDatabase opens nothing. - Preferences gains a Database section: Move to... (copies, leaves the original), Open Existing..., Use Default Location. Refuses foreign SQLite files and unwritable ones. - Models call JobsDatabase::reopenIfPathChanged() on refresh, so switching databases needs no restart. - Link KF6::ConfigCore; add a QuickDialogs2 configure-time guard; add kconfig to the Arch dependencies and a note to the Flatpak manifest. - README documents --db, the precedence, and the first-run choice, and gains Fedora 44 build dependencies; CONTRIBUTING gets a dev recipe. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01BxDf7HqD1xPnsZP8wt3NTk
107 lines
2.0 KiB
C++
107 lines
2.0 KiB
C++
/*
|
|
SPDX-FileCopyrightText: 2026 ToServeTheKing <[email protected]>
|
|
|
|
SPDX-License-Identifier: GPL-3.0-or-later
|
|
*/
|
|
|
|
#include "statsmodel.h"
|
|
#include "jobstage.h"
|
|
|
|
using namespace Qt::Literals::StringLiterals;
|
|
|
|
StatsModel::StatsModel(QObject *parent)
|
|
: QObject(parent)
|
|
{
|
|
refresh();
|
|
}
|
|
|
|
void StatsModel::refresh()
|
|
{
|
|
m_db.reopenIfPathChanged();
|
|
m_jobs = m_db.allJobs();
|
|
Q_EMIT changed();
|
|
}
|
|
|
|
int StatsModel::totalApplications() const
|
|
{
|
|
return m_jobs.size();
|
|
}
|
|
|
|
int StatsModel::activeApplications() const
|
|
{
|
|
int count = 0;
|
|
for (const Job &job : m_jobs) {
|
|
if (!JobStage::isTerminal(job.stage)) {
|
|
++count;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
int StatsModel::offerCount() const
|
|
{
|
|
int count = 0;
|
|
for (const Job &job : m_jobs) {
|
|
if (job.stage == QLatin1String("Offer") || job.stage == QLatin1String("Accepted")) {
|
|
++count;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
int StatsModel::acceptedCount() const
|
|
{
|
|
int count = 0;
|
|
for (const Job &job : m_jobs) {
|
|
if (job.stage == QLatin1String("Accepted")) {
|
|
++count;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
int StatsModel::rejectedCount() const
|
|
{
|
|
int count = 0;
|
|
for (const Job &job : m_jobs) {
|
|
if (job.stage == QLatin1String("Rejected")) {
|
|
++count;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
double StatsModel::responseRate() const
|
|
{
|
|
if (m_jobs.isEmpty()) {
|
|
return 0.0;
|
|
}
|
|
int responded = 0;
|
|
for (const Job &job : m_jobs) {
|
|
if (job.stage != QLatin1String("Applied")) {
|
|
++responded;
|
|
}
|
|
}
|
|
return 100.0 * responded / m_jobs.size();
|
|
}
|
|
|
|
double StatsModel::offerRate() const
|
|
{
|
|
if (m_jobs.isEmpty()) {
|
|
return 0.0;
|
|
}
|
|
return 100.0 * offerCount() / m_jobs.size();
|
|
}
|
|
|
|
QVariantMap StatsModel::stageCounts() const
|
|
{
|
|
QVariantMap counts;
|
|
for (const QString &stage : JobStage::canonicalStages()) {
|
|
counts.insert(stage, 0);
|
|
}
|
|
for (const Job &job : m_jobs) {
|
|
counts[job.stage] = counts.value(job.stage, 0).toInt() + 1;
|
|
}
|
|
return counts;
|
|
}
|