feat: add TechLog project decision authoring

This commit is contained in:
DongHyeonka
2026-08-17 17:23:42 +09:00
parent 79e9aa8328
commit 0355b644a0
16 changed files with 532 additions and 33 deletions
@@ -8,7 +8,8 @@ function ordered(relations: RelationInput[]): RelationInput[] {
return relations.map((relation, order) => ({ ...relation, order }));
}
export function RelationEditor({ relations, catalog, onChange }: { relations: RelationInput[]; catalog: CatalogEntry[]; onChange(relations: RelationInput[]): void }) {
export function RelationEditor({ relations, catalog, evidence = false, onChange }: { relations: RelationInput[]; catalog: CatalogEntry[]; evidence?: boolean; onChange(relations: RelationInput[]): void }) {
const noun = evidence ? "근거" : "관계";
const update = (index: number, patch: Partial<RelationInput>) => onChange(ordered(relations.map((relation, candidateIndex) => candidateIndex === index ? { ...relation, ...patch } : relation)));
const move = (index: number, delta: -1 | 1) => {
const target = index + delta;
@@ -19,16 +20,16 @@ export function RelationEditor({ relations, catalog, onChange }: { relations: Re
};
return (
<fieldset className="studio-ordered-list">
<legend></legend>
{relations.length === 0 ? <p> .</p> : null}
<legend>{evidence ? "근거 기록" : "관계"}</legend>
{relations.length === 0 ? <p>{evidence ? "연결한 근거 기록이 없습니다." : "연결한 공개 기록이 없습니다."}</p> : null}
{relations.map((relation, index) => (
<div className="studio-relation-item" key={relation.id ?? `relation-${index}`}>
<label><span> {index + 1} </span><select value={relation.targetId ?? ""} onChange={(event) => update(index, { targetId: event.currentTarget.value || null })}><option value=""> </option>{catalog.map((entry) => <option key={entry.id} value={entry.id} disabled={relations.some((candidate, candidateIndex) => candidateIndex !== index && candidate.targetId === entry.id)}>{entry.label}</option>)}</select></label>
<label><span> {index + 1} </span><input value={relation.reason} onChange={(event) => update(index, { reason: event.currentTarget.value })} /></label>
<label><span>{noun} {index + 1} </span><select value={relation.targetId ?? ""} onChange={(event) => update(index, { targetId: event.currentTarget.value || null })}><option value=""> </option>{catalog.map((entry) => <option key={entry.id} value={entry.id} disabled={relations.some((candidate, candidateIndex) => candidateIndex !== index && candidate.targetId === entry.id)}>{entry.label}</option>)}</select></label>
<label><span>{noun} {index + 1} </span><input value={relation.reason} onChange={(event) => update(index, { reason: event.currentTarget.value })} /></label>
<div className="studio-item-actions"><button type="button" onClick={() => move(index, -1)} disabled={index === 0}></button><button type="button" onClick={() => move(index, 1)} disabled={index === relations.length - 1}></button><button type="button" onClick={() => onChange(ordered(relations.filter((_, candidateIndex) => candidateIndex !== index)))}></button></div>
</div>
))}
<button className="studio-add-item" type="button" disabled={relations.length >= 20} onClick={() => { if (relations.length < 20) onChange(ordered([...relations, { id: createLocalId("relation"), targetId: null, reason: "", order: relations.length }])); }}> </button>
<button className="studio-add-item" type="button" disabled={relations.length >= 20} onClick={() => { if (relations.length < 20) onChange(ordered([...relations, { id: createLocalId("relation"), targetId: null, reason: "", order: relations.length }])); }}>{noun} </button>
</fieldset>
);
}