diff --git a/src/App.jsx b/src/App.jsx index 83f4807..7b23b63 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1238,11 +1238,33 @@ function ProgramComparePopup({ programs, comparisons, onComparisonChange, onClos const rightProgram = programs.find((program) => program.id === rightProgramId); const comparison = comparisons.find((item) => item.leftProgramId === leftProgramId && item.rightProgramId === rightProgramId) ?? {}; + const leftSteps = leftProgram?.steps ?? []; + const rightSteps = rightProgram?.steps ?? []; + const stepMatches = comparison.stepMatches ?? []; const updateComparison = (field, value) => { if (!leftProgramId || !rightProgramId || leftProgramId === rightProgramId) return; onComparisonChange(leftProgramId, rightProgramId, field, value); }; + const createStepMatch = () => ({ + id: `${Date.now()}-${Math.random().toString(36).slice(2)}`, + leftStepIndex: '0', + rightStepIndex: '0', + reason: '' + }); + const addStepMatch = () => { + updateComparison('stepMatches', [...stepMatches, createStepMatch()]); + }; + const updateStepMatch = (matchIndex, field, value) => { + updateComparison( + 'stepMatches', + stepMatches.map((match, index) => (index === matchIndex ? { ...match, [field]: value } : match)) + ); + }; + const removeStepMatch = (matchIndex) => { + updateComparison('stepMatches', stepMatches.filter((_, index) => index !== matchIndex)); + }; + const getStepLabel = (step, index) => `${index + 1}. ${step?.title ?? '-'}`; const renderProgramSummary = (program) => { if (!program) return null; @@ -1298,7 +1320,13 @@ function ProgramComparePopup({ programs, comparisons, onComparisonChange, onClos 비교 프로그램 A setLeftProgramId(event.target.value)} + onChange={(event) => { + const nextProgramId = event.target.value; + setLeftProgramId(nextProgramId); + if (nextProgramId === rightProgramId) { + setRightProgramId(programs.find((program) => program.id !== nextProgramId)?.id ?? ''); + } + }} className="mt-1 w-full rounded-2xl border border-slate-200 bg-slate-50 px-3 py-2 text-sm font-extrabold text-slate-800 outline-none focus:border-blue-400" > {programs.map((program) => ( @@ -1329,44 +1357,99 @@ function ProgramComparePopup({ programs, comparisons, onComparisonChange, onClos {renderProgramSummary(rightProgram)} - - - A에서 안 되는 기능 - updateComparison('missingFeature', event.target.value)} - rows={4} - placeholder="예: 3D 형상 직접 편집, obj/ifc 포맷 변환 등" - className="mt-1 w-full resize-y rounded-2xl border border-amber-100 bg-amber-50/50 px-3 py-2 text-sm font-bold leading-6 text-slate-800 outline-none focus:border-amber-300" - /> - - - B에서 수행하는 기능 - updateComparison('alternativeFeature', event.target.value)} - rows={4} - placeholder="예: 모델 방향 수정, 속성정보 입력, 포맷 변환" - className="mt-1 w-full resize-y rounded-2xl border border-blue-100 bg-blue-50/50 px-3 py-2 text-sm font-bold leading-6 text-slate-800 outline-none focus:border-blue-300" - /> - - - 사용 이유 / 결론 - updateComparison('reason', event.target.value)} - rows={3} - placeholder="예: 사내 프로그램에서 해당 편집 기능이 없어 현재는 상용 프로그램을 보완 도구로 사용" - className="mt-1 w-full resize-y rounded-2xl border border-slate-200 bg-slate-50 px-3 py-2 text-sm font-bold leading-6 text-slate-800 outline-none focus:border-slate-400" - /> - - - 비고 + + + + 스텝 1:1 매칭 + + 왼쪽 스텝과 오른쪽 스텝을 연결하고, 가운데에 상용 프로그램을 쓰는 이유를 적습니다. + + + + + 매칭 추가 + + + + + {stepMatches.length === 0 ? ( + + 아직 매칭된 스텝이 없습니다. `매칭 추가`를 눌러 스텝을 연결하세요. + + ) : ( + stepMatches.map((match, matchIndex) => ( + + + {leftProgram?.name ?? 'A'} 스텝 + updateStepMatch(matchIndex, 'leftStepIndex', event.target.value)} + className="mt-1 w-full rounded-xl border border-amber-100 bg-white px-3 py-2 text-[12px] font-extrabold text-slate-800 outline-none focus:border-amber-300" + > + {leftSteps.map((step, index) => ( + + {getStepLabel(step, index)} + + ))} + + + {leftSteps[Number(match.leftStepIndex ?? 0)]?.feature || '-'} + + + + 안 되는 기능 / 상용 사용 이유 + updateStepMatch(matchIndex, 'reason', event.target.value)} + rows={5} + placeholder="예: 이 스텝에서 3D 형상 수정과 obj/ifc 변환이 안 돼서 Rhino의 모델 편집/포맷 변환 기능을 사용" + className="mt-1 h-[128px] w-full resize-y rounded-xl border border-slate-200 bg-white px-3 py-2 text-[12px] font-bold leading-5 text-slate-800 outline-none focus:border-blue-300" + /> + + + {rightProgram?.name ?? 'B'} 스텝 + updateStepMatch(matchIndex, 'rightStepIndex', event.target.value)} + className="mt-1 w-full rounded-xl border border-blue-100 bg-white px-3 py-2 text-[12px] font-extrabold text-slate-800 outline-none focus:border-blue-300" + > + {rightSteps.map((step, index) => ( + + {getStepLabel(step, index)} + + ))} + + + {rightSteps[Number(match.rightStepIndex ?? 0)]?.feature || '-'} + + + removeStepMatch(matchIndex)} + className="flex h-10 w-10 items-center justify-center self-start rounded-full bg-white text-slate-400 ring-1 ring-slate-200 hover:bg-red-50 hover:text-red-500" + aria-label="스텝 매칭 삭제" + > + + + + )) + )} + + + + 비교 결론 updateComparison('note', event.target.value)} rows={2} - placeholder="추가 검토사항이나 향후 대체 가능성 등을 적어주세요." + placeholder="예: 현재는 해당 스텝 보완을 위해 상용 프로그램 사용이 필요함." className="mt-1 w-full resize-y rounded-2xl border border-slate-200 bg-white px-3 py-2 text-sm font-bold leading-6 text-slate-700 outline-none focus:border-slate-400" />
+ 왼쪽 스텝과 오른쪽 스텝을 연결하고, 가운데에 상용 프로그램을 쓰는 이유를 적습니다. +
+ {leftSteps[Number(match.leftStepIndex ?? 0)]?.feature || '-'} +
+ {rightSteps[Number(match.rightStepIndex ?? 0)]?.feature || '-'} +