import { useCallback } from "react"; import type { Dispatch, SetStateAction } from "react"; import { ApiError } from "@/api/http"; import { createSection, createSectionCommit, fetchSectionCommits, fetchSections, openSectionEditor, restoreSectionCommit, submitSection, } from "@/api/sections"; import { buildEditorSnapshot, normalizeEditorSnapshot } from "@/lib/editor/snapshot/editorSnapshot"; import type { Change } from "@/lib/editor/draft/editorTypes"; import type { CreatedEntitySummary, PendingEntityCreate } from "@/lib/editor/session/sessionTypes"; import type { Feature, FeatureCollection, FeatureId } from "@/types/geo"; import type { EditorSnapshot, Section, SectionCommit, SectionState } from "@/types/sections"; type EditorDraftApi = { draft: FeatureCollection; buildPayload: () => Change[]; clearChanges: () => void; hasPersistedFeature: (id: Feature["properties"]["id"]) => boolean; }; type Options = { editor: EditorDraftApi; editorUserId: string; emptyFeatureCollection: FeatureCollection; activeSection: Section | null; sectionState: SectionState | null; selectedSectionId: string; newSectionTitle: string; pendingSaveCount: number; pendingEntityCreates: PendingEntityCreate[]; lastSectionSnapshot: EditorSnapshot | null; commitTitle: string; commitNote: string; setActiveSection: Dispatch>; setSelectedSectionId: Dispatch>; setSectionState: Dispatch>; setLastSectionSnapshot: Dispatch>; setInitialData: Dispatch>; setSectionCommits: Dispatch>; setPendingEntityCreates: Dispatch>; setCreatedEntities: Dispatch>; setSelectedFeatureId: Dispatch>; setEntityFormStatus: Dispatch>; setEntityStatus: Dispatch>; setIsSaving: Dispatch>; setIsSubmitting: Dispatch>; setIsOpeningSection: Dispatch>; setAvailableSections: Dispatch>; setNewSectionTitle: Dispatch>; setCommitTitle: Dispatch>; setCommitNote: Dispatch>; }; export function useSectionCommands(options: Options) { const openSectionForEditing = useCallback(async (sectionId: string) => { const editorPayload = await openSectionEditor(sectionId, options.editorUserId); const snapshot = normalizeEditorSnapshot(editorPayload.snapshot); const commits = await fetchSectionCommits(sectionId); const nextInitialData = snapshot?.editor_feature_collection || options.emptyFeatureCollection; options.setActiveSection(editorPayload.section); options.setSelectedSectionId(editorPayload.section.id); options.setSectionState(editorPayload.state); options.setLastSectionSnapshot(snapshot); options.setInitialData(nextInitialData); options.setSectionCommits(commits); options.setPendingEntityCreates([]); options.setCreatedEntities([]); options.setSelectedFeatureId(null); options.setEntityFormStatus(null); }, [options]); const commitSection = useCallback(async () => { if (!options.activeSection || !options.sectionState) { options.setEntityStatus("Chưa mở được section editor."); return; } const geometryChanges = options.editor.buildPayload(); options.setIsSaving(true); options.setEntityStatus(null); try { const snapshot = buildEditorSnapshot({ section: options.activeSection, draft: options.editor.draft, changes: geometryChanges, pendingEntities: options.pendingEntityCreates, previousSnapshot: options.lastSectionSnapshot, hasPersistedFeature: options.editor.hasPersistedFeature, }); const result = await createSectionCommit(options.activeSection.id, { snapshot, created_by: options.editorUserId, expected_version: options.sectionState.version, expected_head_commit_id: options.sectionState.head_commit_id, title: options.commitTitle.trim() || `Commit ${new Date().toLocaleString()}`, note: options.commitNote.trim() || null, }); options.setSectionState(result.state); options.setLastSectionSnapshot(snapshot); options.setInitialData(options.editor.draft); options.editor.clearChanges(); options.setPendingEntityCreates([]); options.setCreatedEntities([]); options.setCommitTitle(""); options.setCommitNote(""); options.setSectionCommits(await fetchSectionCommits(options.activeSection.id)); options.setEntityFormStatus(`Đã tạo commit #${result.commit.commit_no}.`); } catch (err) { if (err instanceof ApiError) { console.error("Commit failed", err.body); options.setEntityStatus(`Commit thất bại: ${err.body}`); return; } console.error("Commit error", err); options.setEntityStatus("Commit thất bại."); } finally { options.setIsSaving(false); } }, [options]); const openSelectedSection = useCallback(async () => { const sectionId = options.selectedSectionId.trim(); if (!sectionId) { options.setEntityStatus("Hãy chọn section để mở."); return; } if (options.pendingSaveCount > 0) { const confirmed = window.confirm("Section hiện tại có thay đổi chưa Commit. Mở section khác sẽ bỏ các thay đổi này. Tiếp tục?"); if (!confirmed) return; } options.setIsOpeningSection(true); options.setEntityStatus(null); try { await openSectionForEditing(sectionId); options.setEntityStatus("Đã mở section để chỉnh sửa."); } catch (err) { if (err instanceof ApiError) { options.setEntityStatus(`Mở section thất bại: ${err.body}`); } else { options.setEntityStatus("Mở section thất bại."); } } finally { options.setIsOpeningSection(false); } }, [openSectionForEditing, options]); const createAndOpenSection = useCallback(async () => { const title = options.newSectionTitle.trim(); if (!title) { options.setEntityStatus("Tên section là bắt buộc."); return; } if (options.pendingSaveCount > 0) { const confirmed = window.confirm("Section hiện tại có thay đổi chưa Commit. Tạo section mới sẽ bỏ các thay đổi này. Tiếp tục?"); if (!confirmed) return; } options.setIsOpeningSection(true); options.setEntityStatus(null); try { const section = await createSection({ title, user_id: options.editorUserId, created_by: options.editorUserId, }); const sections = await fetchSections(); options.setAvailableSections(sections); options.setNewSectionTitle(""); await openSectionForEditing(section.id); options.setEntityStatus("Đã tạo và mở section mới."); } catch (err) { if (err instanceof ApiError) { options.setEntityStatus(`Tạo section thất bại: ${err.body}`); } else { options.setEntityStatus("Tạo section thất bại."); } } finally { options.setIsOpeningSection(false); } }, [openSectionForEditing, options]); const submitCurrentSection = useCallback(async () => { if (!options.activeSection || !options.sectionState?.head_commit_id) { options.setEntityStatus("Section hiện tại chưa có head để submit."); return; } if (options.pendingSaveCount > 0) { options.setEntityStatus("Hãy Commit các thay đổi trước khi Submit."); return; } options.setIsSubmitting(true); options.setEntityStatus(null); try { const submission = await submitSection(options.activeSection.id, { submitted_by: options.editorUserId, }); options.setSectionState((prev) => prev ? { ...prev, status: "submitted" } : prev); options.setEntityStatus(`Đã submit section, submission ${submission.id}.`); } catch (err) { if (err instanceof ApiError) { options.setEntityStatus(`Submit thất bại: ${err.body}`); } else { options.setEntityStatus("Submit thất bại."); } } finally { options.setIsSubmitting(false); } }, [options]); const restoreCommit = useCallback(async (commitId: string) => { if (!options.activeSection || !options.sectionState) { options.setEntityStatus("Chưa mở được section editor."); return; } if (options.pendingSaveCount > 0) { options.setEntityStatus("Hãy Commit hoặc Undo thay đổi hiện tại trước khi Restore."); return; } options.setIsSaving(true); options.setEntityStatus(null); try { const result = await restoreSectionCommit(options.activeSection.id, { commit_id: commitId, created_by: options.editorUserId, expected_version: options.sectionState.version, expected_head_commit_id: options.sectionState.head_commit_id, }); const editorPayload = await openSectionEditor(options.activeSection.id, options.editorUserId); const snapshot = normalizeEditorSnapshot(editorPayload.snapshot); options.setSectionState(result.state); options.setLastSectionSnapshot(snapshot); if (snapshot?.editor_feature_collection) { options.setInitialData(snapshot.editor_feature_collection); } options.setSectionCommits(await fetchSectionCommits(options.activeSection.id)); options.setEntityFormStatus(`Đã restore thành commit #${result.commit.commit_no}.`); } catch (err) { if (err instanceof ApiError) { options.setEntityStatus(`Restore thất bại: ${err.body}`); } else { options.setEntityStatus("Restore thất bại."); } } finally { options.setIsSaving(false); } }, [options]); return { openSectionForEditing, commitSection, openSelectedSection, createAndOpenSection, submitCurrentSection, restoreCommit, }; }