/* ============================================================================= * M01_MasterData_Draft.ts * 고친 것 초안 — sessionStorage 에만 쌓임(자동저장 없음) · [저장] 때만 서버로. * * 요소 파일 = 줄 단위(edits · adds · deletes) · 표형 파일 = 표 하나 통째(tables). * ========================================================================== */ import type { FileChanges, Row } from "./M01_MasterData_Api_Fetch"; /** o = 원래 줄 번호(새 줄은 null) — 노랑 칠은 이 번호의 원래 줄과 견줌. */ export interface TableItem { o: number | null; r: Row; } interface TableDraft { base: Row; items: TableItem[]; } interface FileDraft { version: string; edits: Record; adds: Row[]; deletes: string[]; tables: Record; } const STORAGE_KEY = "m01_master_draft"; const listeners = new Set<() => void>(); function readStore(): Record { try { return JSON.parse(sessionStorage.getItem(STORAGE_KEY) ?? "{}") as Record; } catch { return {}; } } let store = readStore(); export const same = (a: unknown, b: unknown): boolean => JSON.stringify(a) === JSON.stringify(b); /** 구독 — 돌려받은 함수를 부르면 해제. */ export function onDraftChange(fn: () => void): () => void { listeners.add(fn); return () => void listeners.delete(fn); } function touch(file: string): void { const d = store[file]; if ( d && !Object.keys(d.edits).length && !d.adds.length && !d.deletes.length && !Object.keys(d.tables).length ) { delete store[file]; } try { sessionStorage.setItem(STORAGE_KEY, JSON.stringify(store)); } catch { /* 저장소 막힘 — 이 탭 메모리에서만 유지 */ } listeners.forEach((fn) => fn()); } function slot(file: string, version: string): FileDraft { return (store[file] ??= { version, edits: {}, adds: [], deletes: [], tables: {} }); } export function peek(file: string): FileDraft | undefined { return store[file]; } const countOf = (d: FileDraft): number => Object.keys(d.edits).length + d.adds.length + d.deletes.length + Object.keys(d.tables).length; export const fileCount = (file: string): number => (store[file] ? countOf(store[file]) : 0); export const totalCount = (): number => Object.values(store).reduce((n, d) => n + countOf(d), 0); /** 그 파일을 초안이 받은 판본과 다른 판본으로 읽었나 — 저장하면 409 로 막힐 초안. */ export const isStale = (file: string, version: string): boolean => store[file] !== undefined && store[file].version !== version; /* --- 요소 파일 --- */ export function editRow(file: string, version: string, key: string, orig: Row, next: Row): void { const d = slot(file, version); if (same(orig, next)) delete d.edits[key]; else d.edits[key] = next; touch(file); } export function addRow(file: string, version: string, row: Row): void { slot(file, version).adds.push(row); touch(file); } export function setAdd(file: string, index: number, row: Row): void { store[file].adds[index] = row; touch(file); } export function removeAdd(file: string, index: number): void { store[file].adds.splice(index, 1); touch(file); } export function toggleDelete(file: string, version: string, key: string): void { const d = slot(file, version); d.deletes = d.deletes.includes(key) ? d.deletes.filter((k) => k !== key) : [...d.deletes, key]; touch(file); } /* --- 표형 파일 --- */ export function setTable( file: string, version: string, key: string, base: Row, items: TableItem[], orig: Row[], ): void { const d = slot(file, version); if (items.length === orig.length && items.every((it, i) => it.o === i && same(it.r, orig[i]))) { delete d.tables[key]; } else { d.tables[key] = { base, items }; } touch(file); } export const tableItems = (file: string, key: string): TableItem[] | undefined => store[file]?.tables[key]?.items; /* --- 저장·버리기 --- */ export function payload(): FileChanges[] { return Object.entries(store).map(([file, d]) => ({ file, version: d.version, changes: [ ...Object.entries(d.edits) .filter(([key]) => !d.deletes.includes(key)) .map(([key, row]) => ({ op: "edit" as const, key, row })), ...Object.entries(d.tables).map(([key, t]) => ({ op: "edit" as const, key, row: { ...t.base, 줄: t.items.map((it) => it.r) }, })), ...d.adds.map((row) => ({ op: "add" as const, row })), ...d.deletes.map((key) => ({ op: "delete" as const, key })), ], })); } /** files 를 안 주면 전부 버림. */ export function discard(files?: string[]): void { for (const file of files ?? Object.keys(store)) delete store[file]; touch(""); }