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 ? (
) : null}
);
}