import { create } from 'zustand'; import { platform } from '@/lib/platform'; /** * Единый реестр несохранённых изменений (docs/client-tauri.md §5). * * Формы с локальным черновиком регистрируют здесь запись, пока в них есть * правки. Реестр нужен по двум причинам: платформа должна знать, перехватывать * ли закрытие окна (один флаг на всё приложение), а диалог закрытия — уметь * сохранить все формы по очереди, не зная о них ничего. */ /** Сохранение формы: та же операция, что и кнопка «Сохранить». */ export type SaveHandler = () => Promise; export interface UnsavedEntry { id: string; save: SaveHandler; } interface UnsavedState { entries: UnsavedEntry[]; /** Обёртка попросила закрыть окно, а в реестре есть правки. */ closePrompt: boolean; /** * Регистрирует запись и возвращает функцию снятия. Повторная регистрация с * тем же id заменяет обработчик: форма может пересобрать `save`. */ register: (id: string, save: SaveHandler) => () => void; unregister: (id: string) => void; /** Показать диалог закрытия (сообщение `closeRequested` от обёртки). */ requestClose: () => void; /** Закрыть диалог без выхода: пользователь выбрал «Отмена». */ dismissClose: () => void; /** * Сохранить все формы по очереди. Первая же ошибка останавливает процесс и * уходит наверх: диалог покажет её текст, а окно останется открытым. */ saveAll: () => Promise; /** Сброс для тестов и выхода из аккаунта. */ reset: () => void; } const defaults = { entries: [] as UnsavedEntry[], closePrompt: false, }; export const useUnsavedStore = create((set, get) => ({ ...defaults, register: (id, save) => { const entries = get().entries; const index = entries.findIndex((entry) => entry.id === id); if (index === -1) { set({ entries: [...entries, { id, save }] }); } else { const next = [...entries]; next[index] = { id, save }; set({ entries: next }); } return () => get().unregister(id); }, unregister: (id) => { const entries = get().entries; if (!entries.some((entry) => entry.id === id)) { return; } set({ entries: entries.filter((entry) => entry.id !== id) }); }, requestClose: () => set({ closePrompt: true }), dismissClose: () => set({ closePrompt: false }), saveAll: async () => { // Копия списка: `save` может изменить реестр (например, форма сбросит // черновик и снимет регистрацию), а обход должен остаться устойчивым. for (const entry of [...get().entries]) { await entry.save(); } }, reset: () => set({ ...defaults }), })); /** Есть ли в реестре хотя бы одна несохранённая форма. */ export function hasUnsavedChanges(state: UnsavedState): boolean { return state.entries.length > 0; } /** * Платформа узнаёт о переходе «есть правки / нет правок» ровно один раз: * сравниваем агрегат, а не длину списка. Иначе сохранение одной из двух форм * мигало бы `setDirty(false)` → `setDirty(true)`, и обёртка успела бы решить, * что закрывать окно можно. */ useUnsavedStore.subscribe((state, previous) => { const dirty = hasUnsavedChanges(state); if (dirty !== hasUnsavedChanges(previous)) { platform.setDirty(dirty); } });