import { useCallback, useEffect, useState, type FormEvent } from "react"; import type { CatalogEntry } from "../../../contracts/studio/contract.ts"; import type { HomeFocusResponse } from "../../../contracts/management/contract.ts"; import type { ProjectIndexItem } from "../../../contracts/management/contract.ts"; import { managementFailureMessage } from "../../../application/ports/management-gateway-error.ts"; import { useStudio } from "../use-studio.ts"; /** * 공개 홈의 "지금 집중하는 것" 을 정하는 화면. * *

그 영역은 세 칸(현재 작업·열린 질문·최근 결정)을 가지며, 셋이 모두 비면 홈은 영역 자체를 * 그리지 않는다. `home_focus_config` 는 마이그레이션이 빈 행 하나만 넣어 두었고 그 값을 채울 * 화면이 없었으므로, 홈에서는 그 영역이 한 번도 나타난 적이 없었다. * *

고를 수 있는 것은 실제로 존재하는 기록뿐이다. 질문과 결정은 Studio catalog 의 * `RELATION` 목록에서 가져온다 — 그 목록이 곧 "연결 가능한 대상" 의 정의이고, 여기서 다른 * 기준을 쓰면 두 화면이 서로 다른 것을 보여 준다. * *

비공개 프로젝트도 고를 수 있게 둔다. 미리 지목해 두고 게시와 동시에 홈에 뜨게 하는 것이 * 정상적인 순서이기 때문이다. 다만 게시되지 않은 동안에는 홈이 그 칸을 그리지 않으므로, 목록에 * 그 사실을 적어 둔다. */ export function HomeFocusEditor() { const { gateway, managementGateway, setRequestAnnouncement } = useStudio(); const [focus, setFocus] = useState(null); const [projects, setProjects] = useState([]); const [relations, setRelations] = useState([]); const [projectId, setProjectId] = useState(""); const [questionId, setQuestionId] = useState(""); const [decisionId, setDecisionId] = useState(""); const [pending, setPending] = useState(false); const [error, setError] = useState(""); const [generation, setGeneration] = useState(0); const reload = useCallback(() => setGeneration((value) => value + 1), []); useEffect(() => { let cancelled = false; void Promise.all([ managementGateway.getHomeFocus(), managementGateway.listProjects(0, 50), gateway.getCatalog({ type: "RELATION", limit: 100 }), ]).then( ([current, projectPage, catalog]) => { if (cancelled) return; setFocus(current); setProjects(projectPage.items ?? []); setRelations(catalog.items ?? []); setProjectId(current.currentProjectId ?? ""); setQuestionId(current.openQuestionId ?? ""); setDecisionId(current.recentDecisionId ?? ""); setError(""); }, () => { if (!cancelled) setError("홈 설정을 불러오지 못했습니다."); }, ); return () => { cancelled = true; }; }, [gateway, managementGateway, generation]); const questions = relations.filter((entry) => entry.kind === "QUESTION"); const decisions = relations.filter((entry) => entry.kind === "PROJECT_DECISION"); const save = async (event: FormEvent) => { event.preventDefault(); if (pending || !focus) return; setPending(true); setError(""); try { const saved = await managementGateway.updateHomeFocus({ expectedVersion: focus.version, // 빈 문자열은 "고르지 않음" 이다. 계약은 uuid 만 받으므로 보내지 않는다. currentProjectId: projectId || undefined, openQuestionId: questionId || undefined, recentDecisionId: decisionId || undefined, }); setFocus(saved); setRequestAnnouncement("홈에 표시할 항목을 저장했습니다."); reload(); } catch (failure) { setError(managementFailureMessage(failure, "홈 설정을 저장하지 못했습니다.")); } finally { setPending(false); } }; const chosenProject = projects.find((project) => project.id === projectId); const projectHidden = chosenProject && chosenProject.targetVisibility === "PRIVATE"; const nothingChosen = !projectId && !questionId && !decisionId; return (

홈에 무엇을 띄울까

{error ? (

{error}

) : null} {!focus && !error ? (

홈 설정을 불러오는 중입니다.

) : null} {focus ? (

공개 홈 맨 위 “지금 집중하는 것” 영역입니다. 셋 다 비워 두면 그 영역은 나타나지 않습니다.

{projectHidden ? (

이 프로젝트는 아직 비공개입니다. 주제·프로젝트 화면에서 게시해야 홈에 나타납니다.

) : null}
{questions.length === 0 ? (

아직 Question 문서가 없습니다.

) : null}
{decisions.length === 0 ? (

아직 Decision 문서가 없습니다.

) : null}
{nothingChosen ? (

지금은 아무것도 고르지 않아 홈에 이 영역이 나타나지 않습니다.

) : null}
) : null}
); }