From ea3009e922e5f36abe93f4348bf7bf61ba03cd08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=ED=98=9C=EC=9D=B8?= Date: Thu, 25 Jun 2026 17:47:00 +0900 Subject: [PATCH] Refine program metadata layout --- src/App.jsx | 226 +++++++++++++++++++++++++++++----------------------- 1 file changed, 128 insertions(+), 98 deletions(-) diff --git a/src/App.jsx b/src/App.jsx index bcfa62f..64cf6ba 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -10,7 +10,6 @@ import { Layers3, Map, Maximize2, - Minimize2, Mountain, Plus, Route, @@ -222,6 +221,7 @@ const defaultContent = { steps: cheonjiinFlow.map(({ title, feature, note }) => ({ title, feature, note })), deliverables: cheonjiinDeliverables, format: 'glb', + engine: '', programType: 'internal', programNote: '', predecessors: [], @@ -233,6 +233,7 @@ const defaultContent = { description: '설계조건 입력부터 기본설계 성과물 생성', steps: wayPrimalFlow.map(({ title, feature, note }) => ({ title, feature, note })), format: '', + engine: '', deliverables: ['기본설계 모델'], programType: 'internal', programNote: '', @@ -253,6 +254,7 @@ function normalizeStoredContent(parsed) { ...(parsed.cheonjiin ?? {}), programType: getProgramType(parsed.cheonjiin?.programType ?? defaultContent.cheonjiin.programType), programNote: parsed.cheonjiin?.programNote ?? '', + engine: parsed.cheonjiin?.engine ?? '', predecessors: parsed.cheonjiin?.predecessors ?? [], successors: parsed.cheonjiin?.successors ?? [], mergeGroup: parsed.cheonjiin?.mergeGroup ?? '', @@ -266,6 +268,7 @@ function normalizeStoredContent(parsed) { ...(parsed.wayPrimal ?? {}), programType: getProgramType(parsed.wayPrimal?.programType ?? defaultContent.wayPrimal.programType), programNote: parsed.wayPrimal?.programNote ?? '', + engine: parsed.wayPrimal?.engine ?? '', predecessors: parsed.wayPrimal?.predecessors ?? [], successors: parsed.wayPrimal?.successors ?? [], mergeGroup: parsed.wayPrimal?.mergeGroup ?? '', @@ -283,6 +286,7 @@ function normalizeStoredContent(parsed) { ...program, programType: getProgramType(program.programType), programNote: program.programNote ?? '', + engine: program.engine ?? '', predecessors: program.predecessors ?? [], successors: program.successors ?? [], mergeGroup: program.mergeGroup ?? '', @@ -568,6 +572,8 @@ function FlowRow({ onStepChange, format, onFormatChange, + engine = '', + onEngineChange, deliverables = [], onDeliverableChange, onLabelChange, @@ -676,8 +682,11 @@ function FlowRow({ isRowClickable ? 'cursor-pointer transition hover:-translate-y-0.5 hover:shadow-[0_22px_60px_rgba(15,23,42,0.12)]' : '' }`} > -
-
+
+
{isEditing && onLabelChange ? ( onDescriptionChange(event.target.value)} rows={2} - className="mt-1 w-full min-w-[360px] resize-none rounded-xl border border-slate-200 bg-white/85 px-3 py-1.5 text-sm font-bold tracking-tight text-slate-800 outline-none focus:border-teal-400" + className="mt-1 w-full resize-none rounded-xl border border-slate-200 bg-white/85 px-3 py-1.5 text-sm font-bold tracking-tight text-slate-800 outline-none focus:border-teal-400" /> ) : (

{description}

@@ -752,54 +761,77 @@ function FlowRow({ onChange={(event) => onProgramNoteChange(event.target.value)} rows={2} placeholder="프로그램 노트를 입력하세요" - className="mt-2 w-full min-w-[360px] resize-none rounded-xl border border-amber-100 bg-amber-50/70 px-3 py-2 text-sm font-bold leading-5 text-slate-800 outline-none focus:border-amber-300" + className="mt-2 w-full resize-none rounded-xl border border-amber-100 bg-amber-50/70 px-3 py-2 text-sm font-bold leading-5 text-slate-800 outline-none focus:border-amber-300" /> ) : programNote ? ( -
+
NOTE {programNote}
) : null}
-
+
{onFormatChange && ( -
- 포맷 - {isEditing ? ( - onFormatChange(event.target.value)} - placeholder="예: glb" - className="h-8 w-28 rounded-full border border-slate-200 bg-white/85 px-3 text-[12px] font-extrabold text-slate-700 outline-none focus:border-teal-400" - /> - ) : ( - - {format || '-'} - - )} +
+ 포맷 +
+ {isEditing ? ( + onFormatChange(event.target.value)} + placeholder="예: glb" + className="h-8 w-32 rounded-full border border-slate-200 bg-white/85 px-3 text-[12px] font-extrabold text-slate-700 outline-none focus:border-teal-400" + /> + ) : ( + + {format || '-'} + + )} +
)} {(deliverables.length > 0 || isEditing) && ( -
- 성과물 - {(deliverables.length > 0 ? deliverables : ['']).map((item, index) => ( - isEditing ? ( +
+ 성과물 +
+ {(deliverables.length > 0 ? deliverables : ['']).map((item, index) => ( + isEditing ? ( + onDeliverableChange?.(index, event.target.value)} + placeholder="성과물" + className="h-7 w-28 rounded-full border border-slate-200 bg-white/85 px-3 text-[11px] font-extrabold text-slate-700 outline-none focus:border-teal-400" + /> + ) : splitDeliverableText(item).map((deliverable, deliverableIndex) => ( + + {deliverable} + + )) + ))} +
+
+ )} + {onEngineChange && ( +
+ 사용엔진 +
+ {isEditing ? ( onDeliverableChange?.(index, event.target.value)} - placeholder="성과물" - className="h-7 w-28 rounded-full border border-slate-200 bg-white/85 px-3 text-[11px] font-extrabold text-slate-700 outline-none focus:border-teal-400" + value={engine} + onChange={(event) => onEngineChange(event.target.value)} + placeholder="예: hmeg, gsim" + className="h-8 w-40 rounded-full border border-slate-200 bg-white/85 px-3 text-[12px] font-extrabold text-slate-700 outline-none focus:border-teal-400" /> - ) : splitDeliverableText(item).map((deliverable, deliverableIndex) => ( - - {deliverable} - - )) - ))} + ) : ( + + {engine || '-'} + + )} +
)} {isEditing && onAddStep && ( @@ -1597,16 +1629,30 @@ function ProgramComparePopup({ programs, comparisons, initialPair, onComparisonS

{program.description}

-
- {(program.deliverables ?? []).map((deliverable) => ( - - {deliverable} - - ))} +
+
+ 포맷 + {program.format || '-'} +
+
+ 성과물 +
+ {(program.deliverables ?? []).length > 0 ? ( + (program.deliverables ?? []).map((deliverable) => ( + + {deliverable} + + )) + ) : ( + - + )} +
+
+
+ 사용엔진 + {program.engine || '-'} +
-

- 포맷: {program.format || '-'} -

); }; @@ -2083,8 +2129,7 @@ function RelationTreePanel({ const miniRowGap = fullPage ? 96 : 72; const miniPadding = 34; const miniSideLaneWidth = 96; - const sidebarSizes = [420, 560, 720]; - const resolvedSidebarWidth = sidebarWidth ?? sidebarSizes[0]; + const resolvedSidebarWidth = sidebarWidth ?? 420; const miniViewportWidth = Math.max(290, fullPage ? viewportSize.width - 64 : resolvedSidebarWidth - 24); const miniMaxLevelCount = Math.max(1, ...graphLevels.map((level) => level?.length ?? 0)); const miniGraphWidth = Math.max( @@ -2138,11 +2183,6 @@ function RelationTreePanel({ const miniScaledWidth = miniGraphWidth * miniGraphScale; const mapOffset = fullPage ? 0 : Math.max(0, Math.round((miniScaledWidth - miniViewportWidth) / 2 / miniGraphScale)); const mapTranslateX = fullPage ? Math.max(0, Math.round((miniViewportWidth - miniScaledWidth) / 2)) : -mapOffset; - const canShrinkSidebar = resolvedSidebarWidth > sidebarSizes[0]; - const setSidebarSize = (size) => { - onSidebarWidthChange?.(size); - }; - useEffect(() => { if (!fullPage) return undefined; const updateViewportSize = () => { @@ -2332,29 +2372,19 @@ function RelationTreePanel({ )}
{!fullPage && ( -
-
- - +
+
+ +
-
)}
{relations.length > 0 ? ( @@ -2655,6 +2685,7 @@ export default function App() { programNote: content.cheonjiin.programNote ?? '', steps: editableCheonjiinFlow, format: content.cheonjiin.format, + engine: content.cheonjiin.engine ?? '', programType: getProgramType(content.cheonjiin.programType), predecessors: content.cheonjiin.predecessors ?? [], successors: content.cheonjiin.successors ?? [], @@ -2673,6 +2704,7 @@ export default function App() { programNote: content.wayPrimal.programNote ?? '', steps: editableWayPrimalFlow, format: content.wayPrimal.format, + engine: content.wayPrimal.engine ?? '', programType: getProgramType(content.wayPrimal.programType), predecessors: content.wayPrimal.predecessors ?? [], successors: content.wayPrimal.successors ?? [], @@ -2691,6 +2723,7 @@ export default function App() { programNote: program.programNote ?? '', steps: normalizeProgramSteps(program), format: program.format, + engine: program.engine ?? '', programType: getProgramType(program.programType), predecessors: program.predecessors ?? [], successors: program.successors ?? [], @@ -3455,6 +3488,7 @@ export default function App() { } ], format: '', + engine: '', deliverables: ['성과물'], programType: 'internal', programNote: '', @@ -3709,7 +3743,17 @@ export default function App() { return (
-
+
+ {isEditing && ( + + )} -
- )} updateProgramTitle('cheonjiin', 'programType', value)} format={content.cheonjiin.format} onFormatChange={(value) => updateFormat('cheonjiin', value)} + engine={content.cheonjiin.engine ?? ''} + onEngineChange={(value) => updateProgramTitle('cheonjiin', 'engine', value)} deliverables={content.cheonjiin.deliverables} onDeliverableChange={(index, value) => updateProgramDeliverable('cheonjiin', index, value)} onAddStep={() => addStep('cheonjiin')} @@ -3819,6 +3845,8 @@ export default function App() { onProgramTypeChange={(value) => updateProgramTitle('wayPrimal', 'programType', value)} format={content.wayPrimal.format} onFormatChange={(value) => updateFormat('wayPrimal', value)} + engine={content.wayPrimal.engine ?? ''} + onEngineChange={(value) => updateProgramTitle('wayPrimal', 'engine', value)} deliverables={content.wayPrimal.deliverables ?? []} onDeliverableChange={(index, value) => updateProgramDeliverable('wayPrimal', index, value)} onAddStep={() => addStep('wayPrimal')} @@ -3907,6 +3935,8 @@ export default function App() { onProgramTypeChange={(value) => updateProgram(programIndex, 'programType', value)} format={program.format} onFormatChange={(value) => updateProgram(programIndex, 'format', value)} + engine={program.engine ?? ''} + onEngineChange={(value) => updateProgram(programIndex, 'engine', value)} deliverables={program.deliverables ?? []} onDeliverableChange={(index, value) => updateProgramDeliverable(program.id, index, value)} onAddStep={() => addStep(program.id)}