n8n 連携: 後回しにした小さな問題 4 件を修正

- タスク名・チェックの変更で画面全体を作り直さない (直後のクリックが失われていた)
- チケット選択の番号もエスケープする
- 長さ 0 分・24 時間超の区間は送れない理由を付け、画面でも送信でも外す
- items に dict でない項目があっても 500 にせず、その項目のエラーにする

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-13 07:20:21 +09:00
co-authored by Claude Opus 5.5
parent 9a50480491
commit bcdd469d7a
3 changed files with 83 additions and 7 deletions
+25 -7
View File
@@ -38,7 +38,7 @@ function n8SaveName(sessionId, value) {
}
}
const n8CanSend = (b) => !b.sent && !b.inProgress;
const n8CanSend = (b) => !b.sent && !b.inProgress && !b.problem;
const n8DefaultOn = (b) => n8CanSend(b) && !b.deleted && !(b.overlaps || []).length;
function n8Query() {
@@ -78,6 +78,7 @@ function n8Status(b) {
if (b.changedSinceSent) marks.push('<span class="n8-mark warn" title="送った時点では区間が終わっていなかった。work_log の終了時刻は work-tracker の編集画面で直す">送信後に変化</span>');
if (b.deleted) marks.push('<span class="n8-mark warn" title="一度送ってから work_log で削除された区間">削除済み</span>');
if (b.inProgress) marks.push('<span class="n8-mark warn">進行中</span>');
if (b.problem) marks.push(`<span class="n8-mark warn" title="n8n が受け付けないので送れません">${esc(b.problem)}</span>`);
if ((b.overlaps || []).length) {
const tip = b.overlaps.map((o) => `${o.kind === 'row' ? 'work_log' : '区間'}: ${fmtTime(o.start)}–${fmtTime(o.end)} ${o.task}`).join('\n');
marks.push(`<span class="n8-mark warn" title="${esc(tip)}">重なり ${b.overlaps.length}</span>`);
@@ -112,7 +113,7 @@ function n8ResultHtml() {
function n8PickHtml() {
if (!n8.pick) return '';
const rows = n8.issues
.map((i) => `<li><button class="n8-issue" data-id="${i.id}">#${i.id}</button> <span class="note">[${esc(i.project)}]</span> ${esc(i.subject)}</li>`)
.map((i) => `<li><button class="n8-issue" data-id="${esc(i.id)}">#${esc(i.id)}</button> <span class="note">[${esc(i.project)}]</span> ${esc(i.subject)}</li>`)
.join('');
return `<div class="n8-pick">
<h4>Redmine のチケットを選ぶ</h4>
@@ -149,7 +150,7 @@ function renderN8n() {
<label class="chk"><input type="checkbox" id="n8-same" ${n8.sameSession ? 'checked' : ''}> 同じセッションの区間にも入れる</label>
<span class="spacer"></span>
<button id="n8-toggle">${n8.settings ? '設定を閉じる' : '接続設定'}</button>
<button id="n8-send" class="go" ${d.n8nError || n8.busy || !on.length ? 'disabled' : ''}>選んだ ${on.length} 件を送る</button>
<button id="n8-send" class="go"></button>
</div>
${d.n8nError ? `<p class="err-inline">n8n に接続できません: ${esc(d.n8nError)} (送信済みが分からないので送れません)</p>` : ''}
${n8.settings ? n8Settings() : ''}
@@ -160,18 +161,36 @@ function renderN8n() {
<tbody>${rows || '<tr><td colspan="8" class="empty">この期間の区間はありません</td></tr>'}</tbody></table>
</div>`;
n8Wire();
n8SyncSend();
}
/** 送信ボタンの件数と押せるかどうかだけを更新する (ボタン自体は作り直さない)。 */
function n8SyncSend() {
const btn = $('#n8-send');
if (!btn || !n8.data) return;
const on = n8.data.blocks.filter((b) => n8.checked.has(b.source) && n8CanSend(b)).length;
btn.textContent = `選んだ ${on} 件を送る`;
btn.disabled = Boolean(n8.data.n8nError) || n8.busy || !on;
}
function n8Block(source) {
return n8.data.blocks.find((b) => b.source === source);
}
/** 区間の編集値を変え、必要なら同じセッションの他の区間にも入れて、ブラウザに覚える。 */
/** 区間の編集値を変え、必要なら同じセッションの他の区間にも入れて、ブラウザに覚える。
* 画面は作り直さず、変わった行の入力欄だけ書き換える (作り直すと直後のクリックが失われるため)。 */
function n8SetEdit(source, patch) {
const b = n8Block(source);
const targets = n8.sameSession ? n8.data.blocks.filter((x) => x.sessionId === b.sessionId && !x.sent) : [b];
for (const t of targets) n8.edits.set(t.source, { ...(n8.edits.get(t.source) || {}), ...patch });
n8SaveName(b.sessionId, n8.edits.get(source));
const changed = new Set(targets.map((t) => t.source));
document.querySelectorAll('#n8n-table tbody tr[data-src]').forEach((tr) => {
if (!changed.has(tr.dataset.src)) return;
const e = n8.edits.get(tr.dataset.src);
if ('task' in patch) tr.querySelector('.n8-task').value = e.task;
if ('memo' in patch) tr.querySelector('.n8-memo').value = e.memo;
});
}
function n8Wire() {
@@ -203,7 +222,7 @@ function n8Wire() {
const issue = n8.issues.find((i) => String(i.id) === btn.dataset.id);
n8SetEdit(n8.pick, { task: `[${issue.project}] #${issue.id} ${issue.subject}` });
n8.pick = null;
renderN8n();
renderN8n(); // 選択の枠を閉じるので作り直す
};
});
}
@@ -212,11 +231,10 @@ function n8Wire() {
tr.querySelector('.n8-on').onchange = (e) => {
if (e.target.checked) n8.checked.add(source);
else n8.checked.delete(source);
renderN8n();
n8SyncSend();
};
tr.querySelector('.n8-task').onchange = (e) => {
n8SetEdit(source, { task: e.target.value });
renderN8n();
};
tr.querySelector('.n8-memo').onchange = (e) => {
n8SetEdit(source, { memo: e.target.value });