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
221 lines
8.1 KiB
QML
221 lines
8.1 KiB
QML
/*
|
|
SPDX-FileCopyrightText: 2026 ToServeTheKing <[email protected]>
|
|
|
|
SPDX-License-Identifier: GPL-3.0-or-later
|
|
*/
|
|
|
|
pragma ComponentBehavior: Bound
|
|
|
|
// Styled after KDE System Settings' Audio page (plasma-pa's kcm/ui/main.qml):
|
|
// Kirigami.ListSectionHeader per section, flat rows indented under the
|
|
// header, no card/border around them.
|
|
import QtQuick
|
|
import QtQuick.Controls as QQC2
|
|
import QtQuick.Dialogs
|
|
import QtQuick.Layouts
|
|
import org.kde.kirigami as Kirigami
|
|
import io.github.toservetheking.Kareer
|
|
|
|
Kirigami.ScrollablePage {
|
|
id: root
|
|
|
|
title: i18nc("@title:window", "Preferences")
|
|
|
|
leftPadding: 0
|
|
rightPadding: 0
|
|
// No topPadding: Kirigami.ListSectionHeader (the first thing on the
|
|
// page) already carries its own top padding, and stacking ours on top
|
|
// of that left an oversized gap above "Appearance".
|
|
bottomPadding: Kirigami.Units.gridUnit
|
|
|
|
ColumnLayout {
|
|
width: root.width
|
|
spacing: 0
|
|
|
|
Kirigami.ListSectionHeader {
|
|
Layout.fillWidth: true
|
|
text: i18nc("@title:group", "Appearance")
|
|
}
|
|
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: Kirigami.Units.largeSpacing
|
|
Layout.rightMargin: Kirigami.Units.largeSpacing
|
|
Layout.topMargin: Kirigami.Units.smallSpacing
|
|
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
|
spacing: Kirigami.Units.smallSpacing
|
|
|
|
QQC2.Label {
|
|
Layout.fillWidth: true
|
|
text: i18nc("@label:listbox", "Color scheme")
|
|
}
|
|
QQC2.ComboBox {
|
|
id: colorSchemeCombo
|
|
Layout.fillWidth: true
|
|
|
|
model: AppColorScheme.colorSchemesModel
|
|
textRole: "display"
|
|
|
|
delegate: QQC2.ItemDelegate {
|
|
id: schemeDelegate
|
|
required property var model
|
|
width: colorSchemeCombo.width
|
|
|
|
icon.source: "image://colorScheme/" + schemeDelegate.model.display
|
|
icon.color: "transparent"
|
|
text: schemeDelegate.model.display
|
|
highlighted: schemeDelegate.model.display === AppColorScheme.activeColorSchemeName
|
|
|
|
onClicked: {
|
|
AppColorScheme.activeColorSchemeName = schemeDelegate.model.display;
|
|
colorSchemeCombo.popup.close();
|
|
}
|
|
}
|
|
|
|
// Keep the closed-box label in sync without fighting the popup's own selection state.
|
|
displayText: AppColorScheme.activeColorSchemeName
|
|
}
|
|
}
|
|
|
|
Kirigami.ListSectionHeader {
|
|
Layout.fillWidth: true
|
|
text: i18nc("@title:group", "Database")
|
|
}
|
|
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: Kirigami.Units.largeSpacing
|
|
Layout.rightMargin: Kirigami.Units.largeSpacing
|
|
Layout.topMargin: Kirigami.Units.smallSpacing
|
|
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
|
spacing: Kirigami.Units.smallSpacing
|
|
|
|
QQC2.Label {
|
|
Layout.fillWidth: true
|
|
text: i18nc("@label", "Location")
|
|
}
|
|
QQC2.Label {
|
|
Layout.fillWidth: true
|
|
text: DatabaseLocation.path
|
|
wrapMode: Text.WrapAnywhere
|
|
opacity: 0.7
|
|
}
|
|
|
|
Kirigami.InlineMessage {
|
|
Layout.fillWidth: true
|
|
type: Kirigami.MessageType.Information
|
|
text: i18nc("@info", "Set by the --db option or the KAREER_DB_PATH environment variable, so it can't be changed here.")
|
|
visible: DatabaseLocation.overridden
|
|
}
|
|
Kirigami.InlineMessage {
|
|
Layout.fillWidth: true
|
|
type: Kirigami.MessageType.Error
|
|
text: DatabaseLocation.lastError
|
|
visible: text.length > 0
|
|
}
|
|
Kirigami.InlineMessage {
|
|
Layout.fillWidth: true
|
|
type: Kirigami.MessageType.Positive
|
|
text: DatabaseLocation.lastNotice
|
|
visible: text.length > 0
|
|
}
|
|
|
|
Flow {
|
|
Layout.fillWidth: true
|
|
spacing: Kirigami.Units.smallSpacing
|
|
enabled: !DatabaseLocation.overridden
|
|
|
|
QQC2.Button {
|
|
icon.name: "folder-move"
|
|
text: i18nc("@action:button", "Move to…")
|
|
QQC2.ToolTip.text: i18nc("@info:tooltip", "Copy the database into another folder and use the copy from now on. The current file is left in place.")
|
|
QQC2.ToolTip.visible: hovered
|
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
|
onClicked: moveFolderDialog.open()
|
|
}
|
|
QQC2.Button {
|
|
icon.name: "document-open"
|
|
text: i18nc("@action:button", "Open Existing…")
|
|
onClicked: openFileDialog.open()
|
|
}
|
|
QQC2.Button {
|
|
icon.name: "edit-reset"
|
|
text: i18nc("@action:button", "Use Default Location")
|
|
visible: DatabaseLocation.path !== DatabaseLocation.standardPath
|
|
QQC2.ToolTip.text: xi18nc("@info:tooltip", "Switch to <filename>%1</filename>. Nothing is copied.", DatabaseLocation.standardPath)
|
|
QQC2.ToolTip.visible: hovered
|
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
|
onClicked: DatabaseLocation.useDefault()
|
|
}
|
|
}
|
|
}
|
|
|
|
Kirigami.ListSectionHeader {
|
|
Layout.fillWidth: true
|
|
text: i18nc("@title:group", "Applications")
|
|
}
|
|
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
Layout.leftMargin: Kirigami.Units.largeSpacing
|
|
Layout.rightMargin: Kirigami.Units.largeSpacing
|
|
Layout.topMargin: Kirigami.Units.smallSpacing
|
|
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
|
spacing: Kirigami.Units.smallSpacing
|
|
|
|
QQC2.Switch {
|
|
id: autoGhostSwitch
|
|
Layout.fillWidth: true
|
|
text: i18nc("@option:check", "Mark applications with no response as Ghosted")
|
|
checked: AutoGhost.enabled
|
|
onToggled: AutoGhost.enabled = checked
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
enabled: autoGhostSwitch.checked
|
|
spacing: Kirigami.Units.smallSpacing
|
|
|
|
QQC2.Label {
|
|
text: i18nc("@label:spinbox Mark as ghosted after [N] days", "After")
|
|
}
|
|
QQC2.SpinBox {
|
|
from: 1
|
|
to: 365
|
|
value: AutoGhost.days
|
|
onValueModified: AutoGhost.days = value
|
|
}
|
|
QQC2.Label {
|
|
Layout.fillWidth: true
|
|
text: i18nc("@label:spinbox Mark as ghosted after [N] days", "days without any activity")
|
|
wrapMode: Text.Wrap
|
|
}
|
|
}
|
|
|
|
QQC2.Label {
|
|
Layout.fillWidth: true
|
|
text: i18nc("@info", "Only applications still at Applied are affected. The move is recorded in their history, and you can change the stage back at any time.")
|
|
wrapMode: Text.Wrap
|
|
font: Kirigami.Theme.smallFont
|
|
opacity: 0.7
|
|
}
|
|
}
|
|
}
|
|
|
|
Component.onCompleted: DatabaseLocation.clearMessages()
|
|
|
|
FolderDialog {
|
|
id: moveFolderDialog
|
|
title: i18nc("@title:window", "Move Database To")
|
|
onAccepted: DatabaseLocation.moveTo(selectedFolder)
|
|
}
|
|
|
|
FileDialog {
|
|
id: openFileDialog
|
|
title: i18nc("@title:window", "Open Database")
|
|
fileMode: FileDialog.OpenFile
|
|
nameFilters: [i18nc("@item:inlistbox", "Kareer databases (*.sqlite)"), i18nc("@item:inlistbox", "All files (*)")]
|
|
onAccepted: DatabaseLocation.useFile(selectedFile)
|
|
}
|
|
}
|