Files
Kareer/src/qml/Main.qml
T
austinandClaude Opus 5 eb47a589cb Editable stage history, auto-ghosting, and an Add-form fix
Stage history editor:
- The edit form's Pipeline section is now the application's history:
  one row per step (stage + date), Add Step and Remove Step, kept in
  date order. The last step is the current stage and the first step's
  date the date applied, replacing the separate Stage and Date Applied
  fields. An application logged after the fact (applied, interviewed,
  rejected) keeps its whole path.
- JobsDatabase::replaceStageHistory() rewrites a job's history in one
  transaction (sorted, repeats collapsed, stage and date applied kept in
  step); StageHistoryModel backs the section; JobsModel::addJob() now
  returns the new id so a new application's history can be saved.
- `kareer history <id> [Stage=YYYY-MM-DD ...]` shows or replaces it.

Auto-ghosting:
- Applications still at Applied with no activity (the later of the
  date applied and the last stage change) for more than 30 days move to
  Ghosted, recorded like any stage change. Runs at startup for GUI and
  CLI, after switching databases, and shortly after the setting changes.
- Preferences gains an Applications section: on/off and the number of
  days (kareerrc [AutoGhost]). The GUI shows a passive notification; the
  CLI notes it on stderr so --json output stays clean.

Fixes:
- The Add Application form pre-filled empty text fields with the word
  "undefined" (typing "a" gave "undefineda"): fields with no value
  reached QML as undefined. Every field now gets a typed default.
- Embed the app icon as the window icon fallback, so the window and
  About page show it when running uninstalled.

Tests: history replacement and the after-the-fact Allstate case, the
ghosting rules (fresh/stale/logged-late/reopened/threshold), and empty
new-form fields.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01BxDf7HqD1xPnsZP8wt3NTk
2026-09-11 17:00:45 -05:00

154 lines
4.5 KiB
QML

/*
SPDX-FileCopyrightText: 2026 ToServeTheKing <[email protected]>
SPDX-License-Identifier: GPL-3.0-or-later
*/
pragma ComponentBehavior: Bound
import QtQuick
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
import io.github.toservetheking.Kareer
Kirigami.ApplicationWindow {
id: root
title: i18nc("@title:window", "Kareer")
minimumWidth: Kirigami.Units.gridUnit * 32
minimumHeight: Kirigami.Units.gridUnit * 24
width: Kirigami.Units.gridUnit * 50
height: Kirigami.Units.gridUnit * 36
JobsModel {
id: jobsModel
}
// The database moved (first-run choice or Preferences): reload the list,
// which cascades to the dashboard via countChanged, and leave any open
// edit form, since its job id belonged to the previous database.
Connections {
target: DatabaseLocation
function onChanged(): void {
AutoGhost.run();
jobsModel.refresh();
root.showDashboard();
}
}
// Applications with no response moved to Ghosted (at startup, after a
// database switch, or after the Preferences setting changed).
Connections {
target: AutoGhost
function onRan(count: int): void {
if (count > 0) {
jobsModel.refresh();
root.announceGhosted(count);
}
}
}
function announceGhosted(count: int): void {
root.showPassiveNotification(i18ncp("@info", "Marked %1 application with no response as Ghosted", "Marked %1 applications with no response as Ghosted", count));
}
DatabaseSetupDialog {
id: setupDialog
}
Component.onCompleted: {
if (DatabaseLocation.setupPending) {
setupDialog.open();
} else if (AutoGhost.lastRunCount > 0) {
root.announceGhosted(AutoGhost.lastRunCount);
}
}
// The job currently open in the edit form, so the sidebar can keep it highlighted.
property int currentJobId: -1
pageStack.defaultColumnWidth: Kirigami.Units.gridUnit * 16
pageStack.globalToolBar.style: Kirigami.ApplicationHeaderStyle.ToolBar
globalDrawer: Kirigami.GlobalDrawer {
isMenu: true
actions: [
Kirigami.Action {
text: i18nc("@action:inmenu", "Preferences…")
icon.name: "configure"
onTriggered: root.pageStack.pushDialogLayer(settingsPageComponent, {
width: root.width
}, {
width: Kirigami.Units.gridUnit * 24,
height: Kirigami.Units.gridUnit * 20,
modality: Qt.NonModal
})
},
Kirigami.Action {
text: i18nc("@action:inmenu", "About %1", root.title)
icon.name: "help-about"
onTriggered: root.pageStack.pushDialogLayer(aboutPageComponent, {
width: root.width
}, {
width: Kirigami.Units.gridUnit * 30,
height: Kirigami.Units.gridUnit * 30,
modality: Qt.NonModal
})
}
]
}
Component {
id: aboutPageComponent
FormCard.AboutPage {}
}
Component {
id: settingsPageComponent
SettingsPage {}
}
// Two static columns: the applications list (sidebar) and the dashboard.
// "Add Application" (and clicking a row) replaces just the dashboard
// column with the edit form - the sidebar and its list are untouched.
pageStack.initialPage: [applicationsComponent, dashboardComponent]
function showDashboard(): void {
root.currentJobId = -1;
root.pageStack.currentIndex = 1;
root.pageStack.replace(dashboardComponent);
}
function showEditPage(jobId: int): void {
root.currentJobId = jobId;
root.pageStack.currentIndex = 1;
root.pageStack.replace(editPageComponent, {editingJobId: jobId});
}
Component {
id: applicationsComponent
ApplicationsPage {
jobsModel: jobsModel
currentJobId: root.currentJobId
onEditRequested: jobId => root.showEditPage(jobId)
}
}
Component {
id: dashboardComponent
DashboardPage {
jobsModel: jobsModel
onAddRequested: root.showEditPage(-1)
}
}
Component {
id: editPageComponent
ApplicationEditPage {
jobsModel: jobsModel
onDone: root.showDashboard()
}
}
}