// Everything you owe, grouped by deadline. // // There is no checkbox: a task is closed in Jira, Todoist or Google Tasks, and // the next refresh notices. Two places to tick something off is two places to // be wrong. import QtQuick import QtQuick.Controls import QtQuick.Layouts import "." import "components" ScrollView { id: page contentWidth: availableWidth clip: true function bucketTasks(bucket) { var out = [] for (var i = 0; i < controller.tasks.length; i++) { var task = controller.tasks[i] if (!task.is_done && task.bucket === bucket) out.push(task) } return out } function bucketColor(bucket) { if (bucket === "overdue") return Theme.clay if (bucket === "today") return Theme.amber if (bucket === "active") return Theme.pine if (bucket === "upcoming") return Theme.faint return Theme.rule } ColumnLayout { width: page.availableWidth - 56 x: 28 y: 26 spacing: 22 PageHeader { eyebrow: "From Jira, Todoist, Google Tasks and your own list" title: "Tasks" caption: "Close them where they live — the next refresh credits them to today." GhostButton { text: "Add"; onClicked: addDialog.open() } PrimaryButton { text: "Refresh sources"; onClicked: controller.refreshSources() } } Repeater { model: [ { key: "overdue", label: "Overdue" }, { key: "today", label: "Due today" }, { key: "active", label: "In progress" }, { key: "upcoming", label: "Coming up" }, { key: "someday", label: "No deadline" } ] delegate: ColumnLayout { required property var modelData property var items: page.bucketTasks(modelData.key) visible: items.length > 0 Layout.fillWidth: true spacing: 2 SectionTitle { text: modelData.label; trailing: "" + parent.items.length } Repeater { model: parent.items delegate: Item { required property var modelData Layout.fillWidth: true implicitHeight: row.implicitHeight + 20 Rectangle { anchors.bottom: parent.bottom width: parent.width height: 1 color: Theme.rule } RowLayout { id: row anchors.fill: parent anchors.topMargin: 10 anchors.bottomMargin: 10 spacing: 14 Rectangle { width: 2 Layout.fillHeight: true radius: 1 color: page.bucketColor(modelData.bucket) } ColumnLayout { Layout.fillWidth: true spacing: 3 Label { text: modelData.title color: Theme.ink font.pixelSize: 14 font.weight: modelData.pinned ? Font.DemiBold : Font.Normal font.family: Theme.sansFamily elide: Text.ElideRight Layout.fillWidth: true } RowLayout { spacing: 12 Label { text: modelData.source_label color: Theme.faint; font.pixelSize: 11 font.family: Theme.sansFamily } Label { visible: !!modelData.project && modelData.project !== modelData.source_label text: modelData.project || "" color: Theme.faint; font.pixelSize: 11 font.family: Theme.sansFamily } Label { visible: !!modelData.status text: modelData.status || "" color: Theme.faint; font.pixelSize: 11 font.family: Theme.sansFamily } Label { visible: modelData.days_overdue > 0 text: modelData.days_overdue + "d overdue" color: Theme.clay; font.pixelSize: 11 font.weight: Font.DemiBold font.family: Theme.sansFamily } Label { visible: !!modelData.due_date && modelData.days_overdue === 0 text: "due " + (modelData.due_date || "") color: Theme.faint; font.pixelSize: 11 font.family: Theme.sansFamily } } } GhostButton { text: modelData.pinned ? "Unpin" : "Pin" implicitHeight: 28 onClicked: controller.setTaskPinned(modelData.id, !modelData.pinned) } GhostButton { visible: !!modelData.url text: "Open" implicitHeight: 28 onClicked: controller.openUrl(modelData.url) } GhostButton { text: "Focus" implicitHeight: 28 onClicked: { controller.attachTask(modelData.id) controller.startWork() window.page = 0 } } } } } } } Panel { visible: controller.tasks.length === 0 ColumnLayout { spacing: 8 Label { text: "Nothing on the list" color: Theme.ink font.pixelSize: 17 font.family: Theme.displayFamily } Label { text: "Connect Jira, Todoist or Google Tasks in Settings, or add something yourself." color: Theme.muted font.pixelSize: 12 font.family: Theme.sansFamily Layout.fillWidth: true wrapMode: Text.WordWrap } GhostButton { text: "Open settings"; onClicked: window.page = 4 } } } Item { Layout.preferredHeight: 10 } } Dialog { id: addDialog anchors.centerIn: Overlay.overlay width: 380 modal: true title: "New task" standardButtons: Dialog.Cancel ColumnLayout { anchors.fill: parent spacing: 12 TextField { id: titleField Layout.fillWidth: true placeholderText: "What needs doing?" } TextField { id: dueField Layout.fillWidth: true placeholderText: "Due date — YYYY-MM-DD (optional)" } Label { text: "Local tasks live only here, so they are the one kind you can delete." color: Theme.faint font.pixelSize: 11 font.family: Theme.sansFamily Layout.fillWidth: true wrapMode: Text.WordWrap } PrimaryButton { text: "Add" enabled: titleField.text.trim() !== "" Layout.alignment: Qt.AlignRight onClicked: { controller.createTask(titleField.text.trim(), dueField.text.trim()) titleField.text = ""; dueField.text = "" addDialog.close() } } } } }