/**
* D-Sec ログ(/ds-logs)の備考の「編集モード」の下書き(純粋関数のみ。ユーザー決定 2026-10-19 U3。指示書BC 段階4。D-183)。
*
* ★規則は `lib/prechecks/edit-drafts.ts`(3画面の対応状況・備考)と同じ★ — ただし行に対応状況が無いので別に持つ:
* - ★下書きは「行の値と違う備考」だけを持つ★。行の値に戻したらエントリごと消す(変えた件数 = Map の件数)
* - ★行の null と "" を同じとみなす★(比較は素の文字列。トリムと「空 → 削除」はサーバー側の規則。`app/api/ds-devices/[serial_number]/note/route.ts`)
* - ★`setNoteDraft` は入力の Map を書き換えない★(React の state にそのまま入れるため)
* - ★保存は rows の並び順に1行ずつ★(並列にしない)。★1行の失敗で止めない★
*
* ★期待値表は `lib/ds-devices/__tests__/note-drafts.test.ts`★(N1〜N6c / S1〜S4)。
*/
/** 下書きの対象の行(`DsDevice` が満たす) */
export interface NoteRow {
serial_number: string;
note: string | null;
}
/** serial_number → 入力中の備考 */
export type NoteDrafts = ReadonlyMap<string, string>;
/** 画面に出す備考(下書きがあればそれ、無ければ行の値。★行の null は ""★) */
export function noteDraftValue(row: NoteRow, drafts: NoteDrafts): string {
return drafts.get(row.serial_number) ?? row.note ?? "";
}
/** 備考の下書きを入れる。★新しい Map を返す★。★行の値(null は "")と同じならエントリを消す★ */
export function setNoteDraft(drafts: NoteDrafts, row: NoteRow, note: string): Map<string, string> {
const next = new Map(drafts);
if (note === (row.note ?? "")) next.delete(row.serial_number);
else next.set(row.serial_number, note);
return next;
}
export interface SaveNoteDraftsResult {
/** 成功した行(serial_number → ★サーバーが返した備考★。空にしたら null) */
updated: Map<string, string | null>;
/** 失敗した行(serial_number → 投げられた例外そのまま) */
failed: Map<string, unknown>;
/** ★失敗した行の下書きだけ★ */
remaining: Map<string, string>;
}
/** 下書きをまとめて保存する。★rows の並び順に、下書きが行の値と違う行だけを1行ずつ★ `send` する。★1行の失敗で止めない★ */
export async function saveNoteDrafts<T extends NoteRow>(
rows: readonly T[],
drafts: NoteDrafts,
send: (row: T, note: string) => Promise<string | null>
): Promise<SaveNoteDraftsResult> {
const updated = new Map<string, string | null>();
const failed = new Map<string, unknown>();
const remaining = new Map<string, string>();
for (const row of rows) {
const note = drafts.get(row.serial_number);
if (note === undefined || note === (row.note ?? "")) continue;
try {
updated.set(row.serial_number, await send(row, note));
} catch (error) {
failed.set(row.serial_number, error);
remaining.set(row.serial_number, note);
}
}
return { updated, failed, remaining };
}