From 8f6d848d55ab78884a2912415dafab92cad17deb Mon Sep 17 00:00:00 2001 From: taDuc Date: Tue, 12 May 2026 05:18:54 +0700 Subject: [PATCH] refactor: migrate project data models and transition editor state management to the new project-based API architecture. --- src/app/editor/[id]/page.tsx | 42 +++---- src/app/user/projects/page.tsx | 2 +- src/app/user/wikieditor/page.tsx | 2 +- src/uhm/api/{sections.ts => projects.ts} | 84 +++++++------- src/uhm/components/Editor.tsx | 6 +- .../editor/EntityWikiBindingsPanel.tsx | 2 +- src/uhm/components/editor/ProjectPanel.tsx | 6 +- src/uhm/lib/editor/draft/editorTypes.ts | 2 +- .../useProjectCommands.ts} | 106 +++++++++--------- ...sionState.ts => useProjectSessionState.ts} | 44 ++++---- .../lib/editor/session/useWikiSessionState.ts | 2 +- src/uhm/lib/editor/snapshot/editorSnapshot.ts | 6 +- .../lib/editor/state/useEditorSessionState.ts | 8 +- src/uhm/lib/editor/state/useEditorState.ts | 2 +- src/uhm/types/{sections.ts => projects.ts} | 17 +-- 15 files changed, 162 insertions(+), 169 deletions(-) rename src/uhm/api/{sections.ts => projects.ts} (60%) rename src/uhm/lib/editor/{section/useSectionCommands.ts => project/useProjectCommands.ts} (83%) rename src/uhm/lib/editor/session/{useSectionSessionState.ts => useProjectSessionState.ts} (63%) rename src/uhm/types/{sections.ts => projects.ts} (85%) diff --git a/src/app/editor/[id]/page.tsx b/src/app/editor/[id]/page.tsx index 3069d29..52a8edd 100644 --- a/src/app/editor/[id]/page.tsx +++ b/src/app/editor/[id]/page.tsx @@ -14,7 +14,7 @@ import GeometryBindingPanel from "@/uhm/components/editor/GeometryBindingPanel"; import { Entity, fetchEntities, searchEntitiesByName } from "@/uhm/api/entities"; import { ApiError } from "@/uhm/api/http"; import { fetchCurrentUser } from "@/uhm/api/auth"; -import { SectionCommit } from "@/uhm/api/sections"; +import { ProjectCommit } from "@/uhm/api/projects"; import { searchWikisByTitle, type Wiki } from "@/uhm/api/wikis"; import { searchGeometriesByEntityName, type EntityGeometriesSearchItem, type EntityGeometrySearchGeo } from "@/uhm/api/geometries"; import type { EntitySnapshot } from "@/uhm/types/entities"; @@ -57,13 +57,13 @@ import { loadBackgroundLayerVisibilityFromStorage, persistBackgroundLayerVisibility, } from "@/uhm/lib/editor/background/backgroundVisibilityStorage"; -import { useSectionCommands } from "@/uhm/lib/editor/section/useSectionCommands"; +import { useProjectCommands } from "@/uhm/lib/editor/project/useProjectCommands"; import { EMPTY_FEATURE_COLLECTION } from "@/uhm/lib/map/geo/constants"; import { FIXED_TIMELINE_RANGE, clampYearToFixedRange } from "@/uhm/lib/utils/timeline"; import { useFeatureCommands } from "./featureCommands"; -import { deleteSubmission } from "@/uhm/api/sections"; +import { deleteSubmission } from "@/uhm/api/projects"; import type { WikiSnapshot } from "@/uhm/types/wiki"; -import type { EntityWikiLinkSnapshot } from "@/uhm/types/sections"; +import type { EntityWikiLinkSnapshot } from "@/uhm/types/projects"; import UnifiedSearchBar, { type UnifiedSearchKind } from "@/uhm/components/ui/UnifiedSearchBar"; const CURRENT_YEAR = new Date().getUTCFullYear(); @@ -107,8 +107,8 @@ export default function Page() { isOpeningSection, setIsOpeningSection, setAvailableSections, - selectedSectionId, - setSelectedSectionId, + selectedProjectId, + setSelectedProjectId, newSectionTitle, setNewSectionTitle, commitTitle, @@ -116,10 +116,10 @@ export default function Page() { editorUserIdInput, activeSection, setActiveSection, - sectionState, - setSectionState, + projectState, + setProjectState, sectionCommits, - setSectionCommits, + setProjectCommits, baselineSnapshot, setBaselineSnapshot, entityCatalog, @@ -367,13 +367,13 @@ export default function Page() { + (entitiesDirty ? 1 : 0) + (entityWikiDirty ? 1 : 0); - const sectionCommands = useSectionCommands({ + const sectionCommands = useProjectCommands({ editor, editorUserId, emptyFeatureCollection: EMPTY_FEATURE_COLLECTION, activeSection, - sectionState, - selectedSectionId, + projectState, + selectedProjectId, newSectionTitle, pendingSaveCount, snapshotEntities, @@ -382,11 +382,11 @@ export default function Page() { baselineSnapshot, commitTitle, setActiveSection, - setSelectedSectionId, - setSectionState, + setSelectedProjectId, + setProjectState, setBaselineSnapshot, setInitialData, - setSectionCommits, + setProjectCommits, setSnapshotEntities, setSnapshotWikis, setSnapshotEntityWikiLinks, @@ -1144,8 +1144,8 @@ export default function Page() { } }; - const headCommit = sectionState?.head_commit_id - ? sectionCommits.find((commit) => commit.id === sectionState.head_commit_id) || null + const headCommit = projectState?.head_commit_id + ? sectionCommits.find((commit) => commit.id === projectState.head_commit_id) || null : null; const handleCreateFeature = (feature: Feature) => { @@ -1166,12 +1166,12 @@ export default function Page() { isSaving={isSaving} isSubmitting={isSubmitting} sectionTitle={activeSection?.title || "Đang tải project"} - sectionStatus={sectionState?.status || "editing"} + projectStatus={projectState?.status || "editing"} commitTitle={commitTitle} onCommitTitleChange={setCommitTitle} commitCount={sectionCommits.length} - hasHeadCommit={Boolean(sectionState?.head_commit_id)} - headCommitId={sectionState?.head_commit_id || null} + hasHeadCommit={Boolean(projectState?.head_commit_id)} + headCommitId={projectState?.head_commit_id || null} latestCommitLabel={headCommit ? `Head: ${formatCommitTitle(headCommit)}` : null} commits={sectionCommits} changesCount={pendingSaveCount} @@ -1633,7 +1633,7 @@ function normalizeEditorUserId(value: string): string { return normalized || DEFAULT_EDITOR_USER_ID; } -function formatCommitTitle(commit: SectionCommit): string { +function formatCommitTitle(commit: ProjectCommit): string { return commit.edit_summary?.trim() || `Commit ${commit.id.slice(0, 8)}`; } diff --git a/src/app/user/projects/page.tsx b/src/app/user/projects/page.tsx index 619dd50..93901eb 100644 --- a/src/app/user/projects/page.tsx +++ b/src/app/user/projects/page.tsx @@ -14,7 +14,7 @@ import Badge from "@/components/ui/badge/Badge"; import { CreateProjectPayload, Project } from "@/interface/project"; import { apiCreateProject, apiCreateProjectCommit, apiGetProjectCommits, getCurrentProject } from "@/service/projectService"; import { normalizeEditorSnapshot } from "@/uhm/lib/editor/snapshot/editorSnapshot"; -import type { EditorSnapshot } from "@/uhm/types/sections"; +import type { EditorSnapshot } from "@/uhm/types/projects"; export type ProjectSortColumn = "created_at" | "updated_at" | "title"; diff --git a/src/app/user/wikieditor/page.tsx b/src/app/user/wikieditor/page.tsx index 419b9f4..fd80fa4 100644 --- a/src/app/user/wikieditor/page.tsx +++ b/src/app/user/wikieditor/page.tsx @@ -209,7 +209,7 @@ export default function WikiEditorPage() { type: "doc", content: [ { type: "paragraph", content: [{ type: "text", text: "Write your wiki content here." }] }, - { type: "heading", attrs: { level: 2 }, content: [{ type: "text", text: "Section" }] }, + { type: "heading", attrs: { level: 2 }, content: [{ type: "text", text: "Project" }] }, { type: "paragraph", content: [{ type: "text", text: "Use H1/H2/H3 and the TOC will follow." }] }, ], }, diff --git a/src/uhm/api/sections.ts b/src/uhm/api/projects.ts similarity index 60% rename from src/uhm/api/sections.ts rename to src/uhm/api/projects.ts index c6f3af1..a56585f 100644 --- a/src/uhm/api/sections.ts +++ b/src/uhm/api/projects.ts @@ -2,43 +2,43 @@ import { API_BASE_URL, API_ENDPOINTS } from "@/uhm/api/config"; import { ApiError, jsonRequestInit, requestJson } from "@/uhm/api/http"; import type { CreateCommitInput, - CreateSectionInput, + CreateProjectInput, EditorLoadResponse, RestoreCommitInput, - Section, - SectionCommit, - SectionState, - SectionSubmission, -} from "@/uhm/types/sections"; + Project, + ProjectCommit, + ProjectState, + ProjectSubmission, +} from "@/uhm/types/projects"; export type { CreateCommitInput, - CreateSectionInput, + CreateProjectInput, EditorLoadResponse, RestoreCommitInput, - Section, - SectionCommit, - SectionState, - SectionSubmission, -} from "@/uhm/types/sections"; + Project, + ProjectCommit, + ProjectState, + ProjectSubmission, +} from "@/uhm/types/projects"; -// Sections (API cũ) => Projects (API mới) +// Projects (API cũ) => Projects (API mới) -export async function fetchSections(): Promise { +export async function fetchProjects(): Promise { // /users/current/project requires JWT. - return requestJson(API_ENDPOINTS.currentUserProjects); + return requestJson(API_ENDPOINTS.currentUserProjects); } -export async function createSection(input: CreateSectionInput): Promise
{ +export async function createProject(input: CreateProjectInput): Promise { // POST /projects - return requestJson
(API_ENDPOINTS.projects, jsonRequestInit("POST", input)); + return requestJson(API_ENDPOINTS.projects, jsonRequestInit("POST", input)); } -export async function openSectionEditor(sectionId: string): Promise { +export async function openSectionEditor(projectId: string): Promise { // API mới không có endpoint "editor". FE tự load: // 1) Project details // 2) Project commits (to get snapshot_json of latest commit) - const project = await requestJson
(`${API_ENDPOINTS.projects}/${encodeURIComponent(sectionId)}`); + const project = await requestJson(`${API_ENDPOINTS.projects}/${encodeURIComponent(projectId)}`); const pending = (project.submissions || []).find((s) => s?.status === "PENDING") || null; if (pending) { @@ -51,33 +51,33 @@ export async function openSectionEditor(sectionId: string): Promise c.id === headCommitId) || null : null; const snapshot = headCommit?.snapshot_json ?? null; - const state: SectionState = { + const state: ProjectState = { status: project.project_status || "ACTIVE", head_commit_id: headCommitId, locked_by: project.locked_by ?? null, }; return { - section: project, + project: project, state, commit: headCommit, snapshot, }; } -export async function createSectionCommit( - sectionId: string, +export async function createProjectCommit( + projectId: string, input: CreateCommitInput -): Promise<{ commit: SectionCommit; state: SectionState }> { +): Promise<{ commit: ProjectCommit; state: ProjectState }> { // POST /projects/{id}/commits - const commit = await requestJson( - `${API_ENDPOINTS.projects}/${encodeURIComponent(sectionId)}/commits`, + const commit = await requestJson( + `${API_ENDPOINTS.projects}/${encodeURIComponent(projectId)}/commits`, jsonRequestInit("POST", { snapshot_json: input.snapshot, edit_summary: input.edit_summary, @@ -85,8 +85,8 @@ export async function createSectionCommit( ); // Refresh project state (latest_commit_id may have moved). - const project = await requestJson
(`${API_ENDPOINTS.projects}/${encodeURIComponent(sectionId)}`); - const state: SectionState = { + const project = await requestJson(`${API_ENDPOINTS.projects}/${encodeURIComponent(projectId)}`); + const state: ProjectState = { status: project.project_status || "ACTIVE", head_commit_id: project.latest_commit_id ?? null, locked_by: project.locked_by ?? null, @@ -95,27 +95,27 @@ export async function createSectionCommit( return { commit, state }; } -export async function fetchSectionCommits(sectionId: string): Promise { - return requestJson(`${API_ENDPOINTS.projects}/${encodeURIComponent(sectionId)}/commits`); +export async function fetchProjectCommits(projectId: string): Promise { + return requestJson(`${API_ENDPOINTS.projects}/${encodeURIComponent(projectId)}/commits`); } -export async function restoreSectionCommit( - sectionId: string, +export async function restoreProjectCommit( + projectId: string, input: RestoreCommitInput -): Promise<{ commit: SectionCommit | null; state: SectionState }> { +): Promise<{ commit: ProjectCommit | null; state: ProjectState }> { // POST /projects/{id}/commits/restore await requestJson( - `${API_ENDPOINTS.projects}/${encodeURIComponent(sectionId)}/commits/restore`, + `${API_ENDPOINTS.projects}/${encodeURIComponent(projectId)}/commits/restore`, jsonRequestInit("POST", { commit_id: input.commit_id }) ); // Reload commits + project to determine new head commit. - const project = await requestJson
(`${API_ENDPOINTS.projects}/${encodeURIComponent(sectionId)}`); - const commits = await fetchSectionCommits(sectionId); + const project = await requestJson(`${API_ENDPOINTS.projects}/${encodeURIComponent(projectId)}`); + const commits = await fetchProjectCommits(projectId); const headCommitId = project.latest_commit_id ?? null; const headCommit = headCommitId ? commits.find((c) => c.id === headCommitId) || null : null; - const state: SectionState = { + const state: ProjectState = { status: project.project_status || "ACTIVE", head_commit_id: headCommitId, locked_by: project.locked_by ?? null, @@ -124,18 +124,18 @@ export async function restoreSectionCommit( return { commit: headCommit, state }; } -export async function submitSection(sectionId: string, content: string): Promise { +export async function submitSection(projectId: string, content: string): Promise { // Submit latest commit of project - const project = await requestJson
(`${API_ENDPOINTS.projects}/${encodeURIComponent(sectionId)}`); + const project = await requestJson(`${API_ENDPOINTS.projects}/${encodeURIComponent(projectId)}`); const commitId = project.latest_commit_id; if (!commitId) { throw new Error("Project has no latest commit to submit"); } - return requestJson( + return requestJson( API_ENDPOINTS.submissions, jsonRequestInit("POST", { - project_id: sectionId, + project_id: projectId, commit_id: commitId, content: content, }) diff --git a/src/uhm/components/Editor.tsx b/src/uhm/components/Editor.tsx index e23a85d..df67197 100644 --- a/src/uhm/components/Editor.tsx +++ b/src/uhm/components/Editor.tsx @@ -23,7 +23,7 @@ type Props = { isSaving: boolean; isSubmitting: boolean; sectionTitle: string; - sectionStatus: string; + projectStatus: string; commitTitle: string; onCommitTitleChange: (title: string) => void; commitCount: number; @@ -62,7 +62,7 @@ export default function Editor({ isSaving, isSubmitting, sectionTitle, - sectionStatus, + projectStatus, commitTitle, onCommitTitleChange, commitCount, @@ -109,7 +109,7 @@ export default function Editor({ diff --git a/src/uhm/components/editor/EntityWikiBindingsPanel.tsx b/src/uhm/components/editor/EntityWikiBindingsPanel.tsx index 27a5c91..c4b4b80 100644 --- a/src/uhm/components/editor/EntityWikiBindingsPanel.tsx +++ b/src/uhm/components/editor/EntityWikiBindingsPanel.tsx @@ -3,7 +3,7 @@ import { useEffect, useMemo, useState } from "react"; import type { Entity } from "@/uhm/types/entities"; import type { WikiSnapshot } from "@/uhm/types/wiki"; -import type { EntityWikiLinkSnapshot } from "@/uhm/types/sections"; +import type { EntityWikiLinkSnapshot } from "@/uhm/types/projects"; type EntityChoice = { id: string; name: string }; type WikiChoice = { id: string; title: string; operation?: string }; diff --git a/src/uhm/components/editor/ProjectPanel.tsx b/src/uhm/components/editor/ProjectPanel.tsx index c6a34a5..3c46361 100644 --- a/src/uhm/components/editor/ProjectPanel.tsx +++ b/src/uhm/components/editor/ProjectPanel.tsx @@ -2,14 +2,14 @@ import { Panel } from "./Panel"; type ProjectPanelProps = { sectionTitle: string; - sectionStatus: string; + projectStatus: string; commitCount: number; latestCommitLabel: string | null; }; export function ProjectPanel({ sectionTitle, - sectionStatus, + projectStatus, commitCount, latestCommitLabel, }: ProjectPanelProps) { @@ -18,7 +18,7 @@ export function ProjectPanel({
{sectionTitle}
- Status: {sectionStatus} + Status: {projectStatus}
Commits: {commitCount} diff --git a/src/uhm/lib/editor/draft/editorTypes.ts b/src/uhm/lib/editor/draft/editorTypes.ts index 7131023..202fad5 100644 --- a/src/uhm/lib/editor/draft/editorTypes.ts +++ b/src/uhm/lib/editor/draft/editorTypes.ts @@ -6,7 +6,7 @@ import type { } from "@/uhm/types/geo"; import type { EntitySnapshot } from "@/uhm/types/entities"; import type { WikiSnapshot } from "@/uhm/types/wiki"; -import type { EntityWikiLinkSnapshot } from "@/uhm/types/sections"; +import type { EntityWikiLinkSnapshot } from "@/uhm/types/projects"; export type Change = GeometryChange; diff --git a/src/uhm/lib/editor/section/useSectionCommands.ts b/src/uhm/lib/editor/project/useProjectCommands.ts similarity index 83% rename from src/uhm/lib/editor/section/useSectionCommands.ts rename to src/uhm/lib/editor/project/useProjectCommands.ts index cc8e5df..3d03f47 100644 --- a/src/uhm/lib/editor/section/useSectionCommands.ts +++ b/src/uhm/lib/editor/project/useProjectCommands.ts @@ -2,17 +2,17 @@ import { useCallback } from "react"; import type { Dispatch, SetStateAction } from "react"; import { ApiError } from "@/uhm/api/http"; import { - createSection, - createSectionCommit, - fetchSectionCommits, - fetchSections, + createProject, + createProjectCommit, + fetchProjectCommits, + fetchProjects, openSectionEditor, submitSection, -} from "@/uhm/api/sections"; +} from "@/uhm/api/projects"; import { buildEditorSnapshot, normalizeEditorSnapshot } from "@/uhm/lib/editor/snapshot/editorSnapshot"; import type { Change } from "@/uhm/lib/editor/draft/editorTypes"; import type { Feature, FeatureCollection, FeatureId, GeometryEntitySnapshot, GeometrySnapshot } from "@/uhm/types/geo"; -import type { EditorSnapshot, Section, SectionCommit, SectionState, EntityWikiLinkSnapshot } from "@/uhm/types/sections"; +import type { EditorSnapshot, Project, ProjectCommit, ProjectState, EntityWikiLinkSnapshot } from "@/uhm/types/projects"; import type { EntitySnapshot } from "@/uhm/types/entities"; import type { WikiSnapshot } from "@/uhm/types/wiki"; @@ -27,9 +27,9 @@ type Options = { editor: EditorDraftApi; editorUserId: string; emptyFeatureCollection: FeatureCollection; - activeSection: Section | null; - sectionState: SectionState | null; - selectedSectionId: string; + activeSection: Project | null; + projectState: ProjectState | null; + selectedProjectId: string; newSectionTitle: string; pendingSaveCount: number; snapshotEntities: EntitySnapshot[]; @@ -37,12 +37,12 @@ type Options = { snapshotEntityWikiLinks: EntityWikiLinkSnapshot[]; baselineSnapshot: EditorSnapshot | null; commitTitle: string; - setActiveSection: Dispatch>; - setSelectedSectionId: Dispatch>; - setSectionState: Dispatch>; + setActiveSection: Dispatch>; + setSelectedProjectId: Dispatch>; + setProjectState: Dispatch>; setBaselineSnapshot: Dispatch>; setInitialData: Dispatch>; - setSectionCommits: Dispatch>; + setProjectCommits: Dispatch>; setSnapshotEntities: Dispatch>; setSnapshotWikis: Dispatch>; setSnapshotEntityWikiLinks: Dispatch>; @@ -52,27 +52,27 @@ type Options = { setIsSaving: Dispatch>; setIsSubmitting: Dispatch>; setIsOpeningSection: Dispatch>; - setAvailableSections: Dispatch>; + setAvailableSections: Dispatch>; setNewSectionTitle: Dispatch>; setCommitTitle: Dispatch>; }; -export function useSectionCommands(options: Options) { - const openSectionForEditing = useCallback(async (sectionId: string) => { - const editorPayload = await openSectionEditor(sectionId); +export function useProjectCommands(options: Options) { + const openSectionForEditing = useCallback(async (projectId: string) => { + const editorPayload = await openSectionEditor(projectId); const snapshot = normalizeEditorSnapshot(editorPayload.snapshot); // When starting a fresh editor session from a commit snapshot, treat all rows as baseline state: // operations should not carry over as deltas into the next commit. const sessionSnapshot = snapshot ? toEditorSessionSnapshot(snapshot) : null; - const commits = await fetchSectionCommits(sectionId); + const commits = await fetchProjectCommits(projectId); const nextInitialData = sessionSnapshot?.editor_feature_collection || options.emptyFeatureCollection; - options.setActiveSection(editorPayload.section); - options.setSelectedSectionId(editorPayload.section.id); - options.setSectionState(editorPayload.state); + options.setActiveSection(editorPayload.project); + options.setSelectedProjectId(editorPayload.project.id); + options.setProjectState(editorPayload.state); options.setBaselineSnapshot(sessionSnapshot); options.setInitialData(nextInitialData); - options.setSectionCommits(commits); + options.setProjectCommits(commits); options.setSnapshotEntities(sessionSnapshot?.entities || []); options.setSnapshotWikis(sessionSnapshot?.wikis || []); options.setSnapshotEntityWikiLinks(sessionSnapshot?.entity_wiki || []); @@ -81,8 +81,8 @@ export function useSectionCommands(options: Options) { }, [options]); const commitSection = useCallback(async () => { - if (!options.activeSection || !options.sectionState) { - options.setEntityStatus("Chưa mở được section editor."); + if (!options.activeSection || !options.projectState) { + options.setEntityStatus("Chưa mở được project editor."); return; } if (options.pendingSaveCount <= 0) { @@ -95,7 +95,7 @@ export function useSectionCommands(options: Options) { options.setEntityStatus(null); try { const snapshot = buildEditorSnapshot({ - section: options.activeSection, + project: options.activeSection, draft: options.editor.draft, changes: geometryChanges, snapshotEntities: options.snapshotEntities, @@ -124,13 +124,13 @@ export function useSectionCommands(options: Options) { // If stringify fails, let API call throw a more actionable error downstream. } - const result = await createSectionCommit(options.activeSection.id, { + const result = await createProjectCommit(options.activeSection.id, { snapshot, edit_summary: editSummary, }); const sessionSnapshot = toEditorSessionSnapshot(snapshot); - options.setSectionState(result.state); + options.setProjectState(result.state); options.setBaselineSnapshot(sessionSnapshot); options.setSnapshotEntities(sessionSnapshot.entities || []); options.setSnapshotWikis(sessionSnapshot.wikis || []); @@ -138,7 +138,7 @@ export function useSectionCommands(options: Options) { options.setInitialData(options.editor.draft); options.editor.clearChanges(); options.setCommitTitle(""); - options.setSectionCommits(await fetchSectionCommits(options.activeSection.id)); + options.setProjectCommits(await fetchProjectCommits(options.activeSection.id)); options.setEntityFormStatus("Đã tạo commit."); } catch (err) { if (err instanceof ApiError) { @@ -154,26 +154,26 @@ export function useSectionCommands(options: Options) { }, [options]); const openSelectedSection = useCallback(async () => { - const sectionId = options.selectedSectionId.trim(); - if (!sectionId) { - options.setEntityStatus("Hãy chọn section để mở."); + const projectId = options.selectedProjectId.trim(); + if (!projectId) { + options.setEntityStatus("Hãy chọn project để 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?"); + const confirmed = window.confirm("Project hiện tại có thay đổi chưa Commit. Mở project 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."); + await openSectionForEditing(projectId); + options.setEntityStatus("Đã mở project để chỉnh sửa."); } catch (err) { if (err instanceof ApiError) { - options.setEntityStatus(`Mở section thất bại: ${err.body}`); + options.setEntityStatus(`Mở project thất bại: ${err.body}`); } else { - options.setEntityStatus("Mở section thất bại."); + options.setEntityStatus("Mở project thất bại."); } } finally { options.setIsOpeningSection(false); @@ -183,31 +183,31 @@ export function useSectionCommands(options: Options) { const createAndOpenSection = useCallback(async () => { const title = options.newSectionTitle.trim(); if (!title) { - options.setEntityStatus("Tên section là bắt buộc."); + options.setEntityStatus("Tên project 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?"); + const confirmed = window.confirm("Project hiện tại có thay đổi chưa Commit. Tạo project 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({ + const project = await createProject({ title, description: null, }); - const sections = await fetchSections(); - options.setAvailableSections(sections); + const projects = await fetchProjects(); + options.setAvailableSections(projects); options.setNewSectionTitle(""); - await openSectionForEditing(section.id); - options.setEntityStatus("Đã tạo và mở section mới."); + await openSectionForEditing(project.id); + options.setEntityStatus("Đã tạo và mở project mới."); } catch (err) { if (err instanceof ApiError) { - options.setEntityStatus(`Tạo section thất bại: ${err.body}`); + options.setEntityStatus(`Tạo project thất bại: ${err.body}`); } else { - options.setEntityStatus("Tạo section thất bại."); + options.setEntityStatus("Tạo project thất bại."); } } finally { options.setIsOpeningSection(false); @@ -215,8 +215,8 @@ export function useSectionCommands(options: Options) { }, [openSectionForEditing, options]); const submitCurrentSection = useCallback(async (content: string) => { - if (!options.activeSection || !options.sectionState?.head_commit_id) { - options.setEntityStatus("Section hiện tại chưa có head để submit."); + if (!options.activeSection || !options.projectState?.head_commit_id) { + options.setEntityStatus("Project hiện tại chưa có head để submit."); return; } if (options.pendingSaveCount > 0) { @@ -241,8 +241,8 @@ export function useSectionCommands(options: Options) { }, [options]); const restoreCommit = useCallback(async (commitId: string) => { - if (!options.activeSection || !options.sectionState) { - options.setEntityStatus("Chưa mở được section editor."); + if (!options.activeSection || !options.projectState) { + options.setEntityStatus("Chưa mở được project editor."); return; } if (options.pendingSaveCount > 0) { @@ -255,8 +255,8 @@ export function useSectionCommands(options: Options) { try { // FE-only restore: load snapshot from selected commit and apply to editor state. // Do NOT move project's head commit on backend. - const commits = await fetchSectionCommits(options.activeSection.id); - const target = commits.find((c: SectionCommit) => c.id === commitId) || null; + const commits = await fetchProjectCommits(options.activeSection.id); + const target = commits.find((c: ProjectCommit) => c.id === commitId) || null; if (!target) { options.setEntityStatus("Không tìm thấy commit để restore."); return; @@ -274,8 +274,8 @@ export function useSectionCommands(options: Options) { options.setSelectedFeatureIds([]); options.setEntityFormStatus(null); - // Refresh commits list for UI, but keep sectionState/head as-is. - options.setSectionCommits(commits); + // Refresh commits list for UI, but keep projectState/head as-is. + options.setProjectCommits(commits); options.setEntityFormStatus("Đã load snapshot từ commit (không đổi head trên BE)."); } catch (err) { if (err instanceof ApiError) { diff --git a/src/uhm/lib/editor/session/useSectionSessionState.ts b/src/uhm/lib/editor/session/useProjectSessionState.ts similarity index 63% rename from src/uhm/lib/editor/session/useSectionSessionState.ts rename to src/uhm/lib/editor/session/useProjectSessionState.ts index aa866a8..31c7482 100644 --- a/src/uhm/lib/editor/session/useSectionSessionState.ts +++ b/src/uhm/lib/editor/session/useProjectSessionState.ts @@ -1,15 +1,15 @@ import { useCallback, useState } from "react"; import type { Dispatch, SetStateAction } from "react"; -import type { EditorSnapshot, Section, SectionCommit, SectionState } from "@/uhm/types/sections"; +import type { EditorSnapshot, Project, ProjectCommit, ProjectState } from "@/uhm/types/projects"; type Options = { defaultEditorUserId: string; }; -type SectionTask = "idle" | "saving" | "submitting" | "opening-section"; +type SectionTask = "idle" | "saving" | "submitting" | "opening-project"; -export function useSectionSessionState(options: Options) { - // Single state machine cho các tác vụ async của section (saving/submitting/opening). +export function useProjectSessionState(options: Options) { + // Single state machine cho các tác vụ async của project (saving/submitting/opening). const [sectionTask, setSectionTask] = useState("idle"); const setTaskFlag = useCallback((task: Exclude, next: SetStateAction) => { setSectionTask((prev) => { @@ -22,7 +22,7 @@ export function useSectionSessionState(options: Options) { const isSaving = sectionTask === "saving"; const isSubmitting = sectionTask === "submitting"; - const isOpeningSection = sectionTask === "opening-section"; + const isOpeningSection = sectionTask === "opening-project"; const setIsSaving: Dispatch> = useCallback((next) => { setTaskFlag("saving", next); }, [setTaskFlag]); @@ -30,25 +30,25 @@ export function useSectionSessionState(options: Options) { setTaskFlag("submitting", next); }, [setTaskFlag]); const setIsOpeningSection: Dispatch> = useCallback((next) => { - setTaskFlag("opening-section", next); + setTaskFlag("opening-project", next); }, [setTaskFlag]); - // Danh sách sections để user chọn mở. - const [availableSections, setAvailableSections] = useState([]); - // Section ID đang được chọn trong dropdown. - const [selectedSectionId, setSelectedSectionId] = useState(""); - // Title section mới (để create). + // Danh sách projects để user chọn mở. + const [availableSections, setAvailableSections] = useState([]); + // Project ID đang được chọn trong dropdown. + const [selectedProjectId, setSelectedProjectId] = useState(""); + // Title project mới (để create). const [newSectionTitle, setNewSectionTitle] = useState(""); // Input title cho commit. const [commitTitle, setCommitTitle] = useState(""); // User ID dùng để gắn vào commit/submit/lock. const [editorUserIdInput, setEditorUserIdInput] = useState(options.defaultEditorUserId); - // Section đang mở để edit (null nếu chưa mở). - const [activeSection, setActiveSection] = useState
(null); - // Trạng thái section (version/head/status/lock). - const [sectionState, setSectionState] = useState(null); - // Danh sách commits của section đang mở. - const [sectionCommits, setSectionCommits] = useState([]); + // Project đang mở để edit (null nếu chưa mở). + const [activeSection, setActiveSection] = useState(null); + // Trạng thái project (version/head/status/lock). + const [projectState, setProjectState] = useState(null); + // Danh sách commits của project đang mở. + const [sectionCommits, setProjectCommits] = useState([]); // Baseline snapshot currently loaded for this editor session. const [baselineSnapshot, setBaselineSnapshot] = useState(null); @@ -61,8 +61,8 @@ export function useSectionSessionState(options: Options) { setIsOpeningSection, availableSections, setAvailableSections, - selectedSectionId, - setSelectedSectionId, + selectedProjectId, + setSelectedProjectId, newSectionTitle, setNewSectionTitle, commitTitle, @@ -71,10 +71,10 @@ export function useSectionSessionState(options: Options) { setEditorUserIdInput, activeSection, setActiveSection, - sectionState, - setSectionState, + projectState, + setProjectState, sectionCommits, - setSectionCommits, + setProjectCommits, baselineSnapshot, setBaselineSnapshot, }; diff --git a/src/uhm/lib/editor/session/useWikiSessionState.ts b/src/uhm/lib/editor/session/useWikiSessionState.ts index 7ce1523..38d21f0 100644 --- a/src/uhm/lib/editor/session/useWikiSessionState.ts +++ b/src/uhm/lib/editor/session/useWikiSessionState.ts @@ -1,6 +1,6 @@ import { useState } from "react"; import type { WikiSnapshot } from "@/uhm/types/wiki"; -import type { EntityWikiLinkSnapshot } from "@/uhm/types/sections"; +import type { EntityWikiLinkSnapshot } from "@/uhm/types/projects"; export function useWikiSessionState() { const [snapshotWikis, setSnapshotWikis] = useState([]); diff --git a/src/uhm/lib/editor/snapshot/editorSnapshot.ts b/src/uhm/lib/editor/snapshot/editorSnapshot.ts index 0a1f0a1..a6db1d7 100644 --- a/src/uhm/lib/editor/snapshot/editorSnapshot.ts +++ b/src/uhm/lib/editor/snapshot/editorSnapshot.ts @@ -4,9 +4,9 @@ import type { Change } from "@/uhm/lib/editor/draft/editorTypes"; import type { EntitySnapshot } from "@/uhm/types/entities"; import type { EntitySnapshotOperation } from "@/uhm/types/entities"; import type { Feature, FeatureCollection, GeometryEntitySnapshot, GeometrySnapshot } from "@/uhm/types/geo"; -import type { EditorSnapshot, Section } from "@/uhm/types/sections"; +import type { EditorSnapshot, Project } from "@/uhm/types/projects"; import type { WikiSnapshot } from "@/uhm/types/wiki"; -import type { EntityWikiLinkSnapshot } from "@/uhm/types/sections"; +import type { EntityWikiLinkSnapshot } from "@/uhm/types/projects"; type UnknownRecord = Record; @@ -251,7 +251,7 @@ export function normalizeEditorSnapshot(raw: unknown): EditorSnapshot | null { } export function buildEditorSnapshot(options: { - section: Section; + project: Project; draft: FeatureCollection; changes: Change[]; snapshotEntities: EntitySnapshot[]; diff --git a/src/uhm/lib/editor/state/useEditorSessionState.ts b/src/uhm/lib/editor/state/useEditorSessionState.ts index b518421..642cf8f 100644 --- a/src/uhm/lib/editor/state/useEditorSessionState.ts +++ b/src/uhm/lib/editor/state/useEditorSessionState.ts @@ -2,7 +2,7 @@ import { useState } from "react"; import type { FeatureCollection } from "@/uhm/types/geo"; import { useBackgroundSessionState } from "@/uhm/lib/editor/session/useBackgroundSessionState"; import { useEntitySessionState } from "@/uhm/lib/editor/session/useEntitySessionState"; -import { useSectionSessionState } from "@/uhm/lib/editor/session/useSectionSessionState"; +import { useProjectSessionState } from "@/uhm/lib/editor/session/useProjectSessionState"; import { useTimelineState } from "@/uhm/lib/editor/session/useTimelineState"; import { useWikiSessionState } from "@/uhm/lib/editor/session/useWikiSessionState"; import type { EditorMode, TimelineRange } from "@/uhm/lib/editor/session/sessionTypes"; @@ -24,10 +24,10 @@ type Options = { export function useEditorSessionState(options: Options) { // Mode thao tác map/editor hiện tại. const [mode, setMode] = useState("idle"); - // FeatureCollection "gốc" của session hiện tại (global timeline hoặc section snapshot). + // FeatureCollection "gốc" của session hiện tại (global timeline hoặc project snapshot). const [initialData, setInitialData] = useState(options.emptyFeatureCollection); - const section = useSectionSessionState({ + const project = useProjectSessionState({ defaultEditorUserId: options.defaultEditorUserId, }); const entity = useEntitySessionState(); @@ -43,7 +43,7 @@ export function useEditorSessionState(options: Options) { setMode, initialData, setInitialData, - ...section, + ...project, ...entity, ...timeline, ...background, diff --git a/src/uhm/lib/editor/state/useEditorState.ts b/src/uhm/lib/editor/state/useEditorState.ts index 552a675..2f6e46f 100644 --- a/src/uhm/lib/editor/state/useEditorState.ts +++ b/src/uhm/lib/editor/state/useEditorState.ts @@ -11,7 +11,7 @@ import { useUndoStack } from "@/uhm/lib/editor/draft/useUndoStack"; import type { Change, UndoAction } from "@/uhm/lib/editor/draft/editorTypes"; import type { EntitySnapshot } from "@/uhm/types/entities"; import type { WikiSnapshot } from "@/uhm/types/wiki"; -import type { EntityWikiLinkSnapshot } from "@/uhm/types/sections"; +import type { EntityWikiLinkSnapshot } from "@/uhm/types/projects"; export type { Feature, FeatureCollection, FeatureProperties, Geometry } from "@/uhm/types/geo"; export type { Change, UndoAction } from "@/uhm/lib/editor/draft/editorTypes"; diff --git a/src/uhm/types/sections.ts b/src/uhm/types/projects.ts similarity index 85% rename from src/uhm/types/sections.ts rename to src/uhm/types/projects.ts index 5d4d421..882b525 100644 --- a/src/uhm/types/sections.ts +++ b/src/uhm/types/projects.ts @@ -11,7 +11,7 @@ export type EntityWikiLinkSnapshot = { operation?: "reference" | "binding" | "delete"; }; -// BackEndGo uses Projects/Commits/Submissions. "Section" is legacy naming in FE. +// BackEndGo uses Projects/Commits/Submissions. "Project" is legacy naming in FE. export type ProjectStatus = string; export type ProjectSubmissionStatus = "PENDING" | "APPROVED" | "REJECTED" | string; @@ -71,9 +71,9 @@ export type ProjectSubmission = { }; export type EditorSnapshot = { - // Legacy: before BEGo flow moved fully to project/commit records, FE stored a minimal "section" ref + // Legacy: before BEGo flow moved fully to project/commit records, FE stored a minimal "project" ref // inside snapshot_json. New snapshots omit this entirely. - section?: { + project?: { id: string; title: string; }; @@ -90,13 +90,13 @@ export type EditorSnapshot = { export type CommitSnapshot = EditorSnapshot; export type EditorLoadResponse = { - section: Project; + project: Project; state: ProjectState; commit: ProjectCommit | null; snapshot: EditorSnapshot | null; }; -export type CreateSectionInput = { +export type CreateProjectInput = { title: string; description?: string | null; status?: "PRIVATE" | "PUBLIC" | "ARCHIVE"; @@ -111,10 +111,3 @@ export type RestoreCommitInput = { commit_id: string; }; -// Legacy aliases (to reduce churn in existing FE code). Prefer Project* names above. -export type SectionStatus = ProjectStatus; -export type SectionSubmissionStatus = ProjectSubmissionStatus; -export type SectionState = ProjectState; -export type Section = Project; -export type SectionCommit = ProjectCommit; -export type SectionSubmission = ProjectSubmission;