|
@@ -6,23 +6,58 @@
|
|
|
<script>
|
|
<script>
|
|
|
// The task queue. Plan 12: the filters (status / project / goal)
|
|
// The task queue. Plan 12: the filters (status / project / goal)
|
|
|
// run CLIENT-SIDE - the Statum entrypoint fetch never carries the
|
|
// run CLIENT-SIDE - the Statum entrypoint fetch never carries the
|
|
|
- // page's own query string to the server, so selects filter the
|
|
|
|
|
- // rendered rows directly (window.taskFilters + taskRowVisible)
|
|
|
|
|
- // and survive re-renders; the create form gains the mode select
|
|
|
|
|
- // (with descriptions) and the auto-enqueue-after dropdown (the
|
|
|
|
|
- // global human-task list; goal tasks are the goal agent's to
|
|
|
|
|
- // sequence).
|
|
|
|
|
|
|
+ // page's own query string to the server, so the status chips and
|
|
|
|
|
+ // the selects filter the rendered rows directly (window.taskFilters
|
|
|
|
|
+ // + taskRowVisible) and survive re-renders; the create form gains
|
|
|
|
|
+ // the mode select (with descriptions) and the auto-enqueue-after
|
|
|
|
|
+ // dropdown (the global human-task list; goal tasks are the goal
|
|
|
|
|
+ // agent's to sequence).
|
|
|
|
|
+ //
|
|
|
|
|
+ // Goal 41 T4: the status select became faceted chips with counts
|
|
|
|
|
+ // (the goal-detail strip pattern) and the default view is OPEN
|
|
|
|
|
+ // WORK - queued+assigned+working+blocked+suspended - instead of
|
|
|
|
|
+ // "all", so months of done rows no longer bury the queue (ux-notes
|
|
|
|
|
+ // tasks A+B). taskFilters.status holds the chip state:
|
|
|
|
|
+ // null -> every status (the "All" chip)
|
|
|
|
|
+ // 'open' -> the default open-work set
|
|
|
|
|
+ // '<status>' -> one exact status
|
|
|
if (window.statum) {
|
|
if (window.statum) {
|
|
|
(function () {
|
|
(function () {
|
|
|
'use strict';
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
+ window.TASK_OPEN_STATUSES = ['queued', 'assigned', 'working',
|
|
|
|
|
+ 'blocked', 'suspended'];
|
|
|
|
|
+
|
|
|
window.taskFilters = window.taskFilters || {};
|
|
window.taskFilters = window.taskFilters || {};
|
|
|
|
|
|
|
|
|
|
+ window.taskStatusCount = function (status) {
|
|
|
|
|
+ var tasks = statum.read('tasks');
|
|
|
|
|
+ var rows = (tasks && tasks.tasks) || [];
|
|
|
|
|
+ var count = 0;
|
|
|
|
|
+ for (var i = 0; i < rows.length; i++) {
|
|
|
|
|
+ if (status === 'open') {
|
|
|
|
|
+ if (window.TASK_OPEN_STATUSES.indexOf(rows[i].status) !== -1) {
|
|
|
|
|
+ count += 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (status === 'all') {
|
|
|
|
|
+ count += 1;
|
|
|
|
|
+ } else if (rows[i].status === status) {
|
|
|
|
|
+ count += 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return count;
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
window.taskRowVisible = function (row) {
|
|
window.taskRowVisible = function (row) {
|
|
|
var filters = window.taskFilters || {};
|
|
var filters = window.taskFilters || {};
|
|
|
- if (filters.status && filters.status !== 'all'
|
|
|
|
|
- && row.status !== filters.status) {
|
|
|
|
|
- return false;
|
|
|
|
|
|
|
+ if (filters.status && filters.status !== 'all') {
|
|
|
|
|
+ if (filters.status === 'open') {
|
|
|
|
|
+ if (window.TASK_OPEN_STATUSES.indexOf(row.status) === -1) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (row.status !== filters.status) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
if (filters.project && filters.project > 0
|
|
if (filters.project && filters.project > 0
|
|
|
&& String(row.project_id) !== String(filters.project)) {
|
|
&& String(row.project_id) !== String(filters.project)) {
|
|
@@ -51,27 +86,77 @@
|
|
|
document.body.setAttribute('data-filter-bump', String(Date.now()));
|
|
document.body.setAttribute('data-filter-bump', String(Date.now()));
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
+
|
|
|
|
|
+ window.taskChipClass = function (status) {
|
|
|
|
|
+ var current = (window.taskFilters || {}).status;
|
|
|
|
|
+ if (current == null || current === '') current = 'open';
|
|
|
|
|
+ return current === status ? 'goal-filter active' : 'goal-filter';
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ window.setTaskStatusFilter = function (status) {
|
|
|
|
|
+ var current = (window.taskFilters || {}).status;
|
|
|
|
|
+ if (current == null || current === '') current = 'open';
|
|
|
|
|
+ if (current === status) return; // one chip is always on
|
|
|
|
|
+ window.setTaskFilter('status', status);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // Default to open work until a chip says otherwise.
|
|
|
|
|
+ if (window.taskFilters.status == null) {
|
|
|
|
|
+ window.taskFilters.status = 'open';
|
|
|
|
|
+ }
|
|
|
})();
|
|
})();
|
|
|
}
|
|
}
|
|
|
</script>
|
|
</script>
|
|
|
</head>
|
|
</head>
|
|
|
<body>
|
|
<body>
|
|
|
- <div class="page">
|
|
|
|
|
- <h1>Tasks</h1>
|
|
|
|
|
|
|
+ <div class="stack">
|
|
|
|
|
+
|
|
|
|
|
+ <div class="cluster cluster-between">
|
|
|
|
|
+ <h1>Tasks</h1>
|
|
|
|
|
+ <button type="button" class="btn-primary"
|
|
|
|
|
+ onclick="window.openAppDialog('task-create-dialog')">+ New task</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- Goal 41 T4: faceted status chips with counts (the
|
|
|
|
|
+ goal-strip pattern ported to the queue). One chip is
|
|
|
|
|
+ always active; the default is "Open" - queued, assigned,
|
|
|
|
|
+ working, blocked, suspended - so the queue leads and
|
|
|
|
|
+ done/failed stay one click away with their counts
|
|
|
|
|
+ visible. -->
|
|
|
|
|
+ <p class="goal-strip">
|
|
|
|
|
+ <button type="button" stm-attribute.class="window.taskChipClass('open')"
|
|
|
|
|
+ onclick="window.setTaskStatusFilter('open')"
|
|
|
|
|
+ stm-text="'Open ' + window.taskStatusCount('open')">open</button>
|
|
|
|
|
+ <button type="button" stm-attribute.class="window.taskChipClass('draft')"
|
|
|
|
|
+ onclick="window.setTaskStatusFilter('draft')"
|
|
|
|
|
+ stm-text="'draft ' + window.taskStatusCount('draft')">draft</button>
|
|
|
|
|
+ <button type="button" stm-attribute.class="window.taskChipClass('queued')"
|
|
|
|
|
+ onclick="window.setTaskStatusFilter('queued')"
|
|
|
|
|
+ stm-text="'queued ' + window.taskStatusCount('queued')">queued</button>
|
|
|
|
|
+ <button type="button" stm-attribute.class="window.taskChipClass('assigned')"
|
|
|
|
|
+ onclick="window.setTaskStatusFilter('assigned')"
|
|
|
|
|
+ stm-text="'assigned ' + window.taskStatusCount('assigned')">assigned</button>
|
|
|
|
|
+ <button type="button" stm-attribute.class="window.taskChipClass('working')"
|
|
|
|
|
+ onclick="window.setTaskStatusFilter('working')"
|
|
|
|
|
+ stm-text="'working ' + window.taskStatusCount('working')">working</button>
|
|
|
|
|
+ <button type="button" stm-attribute.class="window.taskChipClass('blocked')"
|
|
|
|
|
+ onclick="window.setTaskStatusFilter('blocked')"
|
|
|
|
|
+ stm-text="'blocked ' + window.taskStatusCount('blocked')">blocked</button>
|
|
|
|
|
+ <button type="button" stm-attribute.class="window.taskChipClass('suspended')"
|
|
|
|
|
+ onclick="window.setTaskStatusFilter('suspended')"
|
|
|
|
|
+ stm-text="'suspended ' + window.taskStatusCount('suspended')">suspended</button>
|
|
|
|
|
+ <button type="button" stm-attribute.class="window.taskChipClass('done')"
|
|
|
|
|
+ onclick="window.setTaskStatusFilter('done')"
|
|
|
|
|
+ stm-text="'done ' + window.taskStatusCount('done')">done</button>
|
|
|
|
|
+ <button type="button" stm-attribute.class="window.taskChipClass('failed')"
|
|
|
|
|
+ onclick="window.setTaskStatusFilter('failed')"
|
|
|
|
|
+ stm-text="'failed ' + window.taskStatusCount('failed')">failed</button>
|
|
|
|
|
+ <button type="button" stm-attribute.class="window.taskChipClass('all')"
|
|
|
|
|
+ onclick="window.setTaskStatusFilter('all')"
|
|
|
|
|
+ stm-text="'all ' + window.taskStatusCount('all')">all</button>
|
|
|
|
|
+ </p>
|
|
|
|
|
|
|
|
<form class="cluster" onsubmit="return false">
|
|
<form class="cluster" onsubmit="return false">
|
|
|
- <label for="filter-status">Status</label>
|
|
|
|
|
- <select id="filter-status" onchange="window.setTaskFilter('status', this.value)">
|
|
|
|
|
- <option value="all">all</option>
|
|
|
|
|
- <option value="draft">draft</option>
|
|
|
|
|
- <option value="queued">queued</option>
|
|
|
|
|
- <option value="assigned">assigned</option>
|
|
|
|
|
- <option value="working">working</option>
|
|
|
|
|
- <option value="blocked">blocked</option>
|
|
|
|
|
- <option value="suspended">suspended</option>
|
|
|
|
|
- <option value="done">done</option>
|
|
|
|
|
- <option value="failed">failed</option>
|
|
|
|
|
- </select>
|
|
|
|
|
<label for="filter-project">Project</label>
|
|
<label for="filter-project">Project</label>
|
|
|
<select id="filter-project" onchange="window.setTaskFilter('project', this.value)">
|
|
<select id="filter-project" onchange="window.setTaskFilter('project', this.value)">
|
|
|
<option value="0">all</option>
|
|
<option value="0">all</option>
|
|
@@ -85,12 +170,55 @@
|
|
|
<option stm-for-goal-in="tasks.goal_options" stm-attribute.value="goal.id"
|
|
<option stm-for-goal-in="tasks.goal_options" stm-attribute.value="goal.id"
|
|
|
stm-text="goal.title">goal</option>
|
|
stm-text="goal.title">goal</option>
|
|
|
</select>
|
|
</select>
|
|
|
- <span class="text-muted">— filters apply instantly</span>
|
|
|
|
|
</form>
|
|
</form>
|
|
|
|
|
|
|
|
- <details class="history-expander">
|
|
|
|
|
- <summary><strong>New task</strong></summary>
|
|
|
|
|
- <form stm-action="tasks.create">
|
|
|
|
|
|
|
+ <div class="table-wrap">
|
|
|
|
|
+ <table class="table-hover">
|
|
|
|
|
+
|
|
|
|
|
+ <thead>
|
|
|
|
|
+ <tr><th>#</th><th>Title</th><th>Status</th><th>Mode</th><th>Goal</th>
|
|
|
|
|
+ <th>Priority</th><th>Project</th><th>Capabilities</th></tr>
|
|
|
|
|
+ </thead>
|
|
|
|
|
+ <tbody>
|
|
|
|
|
+ <tr stm-for-task-in="tasks.tasks" stm-if="window.taskRowVisible(task)">
|
|
|
|
|
+ <td stm-text="task.id">id</td>
|
|
|
|
|
+ <td>
|
|
|
|
|
+ <a stm-attribute.href="'/tasks/' + task.id" stm-text="task.title">title</a>
|
|
|
|
|
+ </td>
|
|
|
|
|
+ <td><span stm-attribute.class="'badge badge-' + task.status" stm-text="task.display">status</span></td>
|
|
|
|
|
+ <td><span stm-attribute.class="'badge badge-mode-' + task.mode" stm-text="task.mode">mode</span></td>
|
|
|
|
|
+ <td stm-if="task.goal_id > 0">
|
|
|
|
|
+ <a stm-attribute.href="'/goals/' + task.goal_id" stm-text="task.goal_title">goal</a>
|
|
|
|
|
+ <span class="text-muted" stm-if="task.creator == 'goal'">(agent)</span>
|
|
|
|
|
+ </td>
|
|
|
|
|
+ <td stm-if="task.goal_id == 0" class="text-muted">—</td>
|
|
|
|
|
+ <td stm-text="task.priority">5</td>
|
|
|
|
|
+ <td stm-text="task.project_name">project</td>
|
|
|
|
|
+ <td><span stm-if="task.capabilities.length > 0"
|
|
|
|
|
+ stm-text="task.capabilities.join(', ')">caps</span>
|
|
|
|
|
+ <span class="text-muted" stm-if="task.capabilities.length == 0">—</span></td>
|
|
|
|
|
+ </tr>
|
|
|
|
|
+ <tr stm-if="tasks.tasks.length == 0"><td colspan="8" class="text-muted">No tasks.</td></tr>
|
|
|
|
|
+ </tbody>
|
|
|
|
|
+
|
|
|
|
|
+ </table>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- Goal 41 T4: the create form moved from a collapsed expander
|
|
|
|
|
+ into a Spry-styled native <dialog> opened by the "+ New
|
|
|
|
|
+ task" button (ux-notes quick win 4). The form itself is
|
|
|
|
|
+ unchanged - same fields, same stm-action - and the shared
|
|
|
|
|
+ openAppDialog helper closes it on the action's notify. -->
|
|
|
|
|
+ <dialog id="task-create-dialog">
|
|
|
|
|
+ <div class="dialog-titlebar">
|
|
|
|
|
+ <p class="dialog-title">New task</p>
|
|
|
|
|
+ <button type="button" class="dialog-close" aria-label="Close"
|
|
|
|
|
+ onclick="window.closeAppDialog('task-create-dialog')">×</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <form stm-action="tasks.create">
|
|
|
|
|
+ <div class="dialog-body form-rows">
|
|
|
<div class="field"><label for="task-project">Project</label>
|
|
<div class="field"><label for="task-project">Project</label>
|
|
|
<!-- Goal 8 task 4: "No project" posts project_id 0 —
|
|
<!-- Goal 8 task 4: "No project" posts project_id 0 —
|
|
|
a STANDALONE task with its own ephemeral
|
|
a STANDALONE task with its own ephemeral
|
|
@@ -149,42 +277,13 @@
|
|
|
stm-attribute.value="capability.id"
|
|
stm-attribute.value="capability.id"
|
|
|
stm-text="capability.name">cap</option>
|
|
stm-text="capability.name">cap</option>
|
|
|
</select></div>
|
|
</select></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="dialog-footer">
|
|
|
|
|
+ <button type="button" class="btn-ghost"
|
|
|
|
|
+ onclick="window.closeAppDialog('task-create-dialog')">Cancel</button>
|
|
|
<button type="submit" class="btn-primary">Create task</button>
|
|
<button type="submit" class="btn-primary">Create task</button>
|
|
|
- </form>
|
|
|
|
|
- </details>
|
|
|
|
|
-
|
|
|
|
|
- <div class="table-wrap">
|
|
|
|
|
- <table>
|
|
|
|
|
-
|
|
|
|
|
- <thead>
|
|
|
|
|
- <tr><th>#</th><th>Title</th><th>Status</th><th>Mode</th><th>Goal</th>
|
|
|
|
|
- <th>Priority</th><th>Project</th><th>Capabilities</th></tr>
|
|
|
|
|
- </thead>
|
|
|
|
|
- <tbody>
|
|
|
|
|
- <tr stm-for-task-in="tasks.tasks" stm-if="window.taskRowVisible(task)">
|
|
|
|
|
- <td stm-text="task.id">id</td>
|
|
|
|
|
- <td>
|
|
|
|
|
- <a stm-attribute.href="'/tasks/' + task.id" stm-text="task.title">title</a>
|
|
|
|
|
- </td>
|
|
|
|
|
- <td><span stm-attribute.class="'badge badge-' + task.status" stm-text="task.display">status</span></td>
|
|
|
|
|
- <td><span stm-attribute.class="'badge badge-mode-' + task.mode" stm-text="task.mode">mode</span></td>
|
|
|
|
|
- <td stm-if="task.goal_id > 0">
|
|
|
|
|
- <a stm-attribute.href="'/goals/' + task.goal_id" stm-text="task.goal_title">goal</a>
|
|
|
|
|
- <span class="text-muted" stm-if="task.creator == 'goal'">(agent)</span>
|
|
|
|
|
- </td>
|
|
|
|
|
- <td stm-if="task.goal_id == 0" class="text-muted">—</td>
|
|
|
|
|
- <td stm-text="task.priority">5</td>
|
|
|
|
|
- <td stm-text="task.project_name">project</td>
|
|
|
|
|
- <td><span stm-if="task.capabilities.length > 0"
|
|
|
|
|
- stm-text="task.capabilities.join(', ')">caps</span>
|
|
|
|
|
- <span class="text-muted" stm-if="task.capabilities.length == 0">—</span></td>
|
|
|
|
|
- </tr>
|
|
|
|
|
- <tr stm-if="tasks.tasks.length == 0"><td colspan="8" class="text-muted">No tasks.</td></tr>
|
|
|
|
|
- </tbody>
|
|
|
|
|
-
|
|
|
|
|
- </table>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </form>
|
|
|
|
|
+ </dialog>
|
|
|
</body>
|
|
</body>
|
|
|
</html>
|
|
</html>
|