Show all steps in program comparison
This commit is contained in:
130
src/App.jsx
130
src/App.jsx
@@ -1241,29 +1241,34 @@ function ProgramComparePopup({ programs, comparisons, onComparisonChange, onClos
|
|||||||
const leftSteps = leftProgram?.steps ?? [];
|
const leftSteps = leftProgram?.steps ?? [];
|
||||||
const rightSteps = rightProgram?.steps ?? [];
|
const rightSteps = rightProgram?.steps ?? [];
|
||||||
const stepMatches = comparison.stepMatches ?? [];
|
const stepMatches = comparison.stepMatches ?? [];
|
||||||
|
const maxStepCount = Math.max(leftSteps.length, rightSteps.length);
|
||||||
|
|
||||||
const updateComparison = (field, value) => {
|
const updateComparison = (field, value) => {
|
||||||
if (!leftProgramId || !rightProgramId || leftProgramId === rightProgramId) return;
|
if (!leftProgramId || !rightProgramId || leftProgramId === rightProgramId) return;
|
||||||
onComparisonChange(leftProgramId, rightProgramId, field, value);
|
onComparisonChange(leftProgramId, rightProgramId, field, value);
|
||||||
};
|
};
|
||||||
const createStepMatch = () => ({
|
const getStepMatch = (stepIndex) =>
|
||||||
id: `${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
stepMatches.find(
|
||||||
leftStepIndex: '0',
|
(match) => String(match.leftStepIndex) === String(stepIndex) && String(match.rightStepIndex) === String(stepIndex)
|
||||||
rightStepIndex: '0',
|
) ?? {};
|
||||||
reason: ''
|
const updateStepReason = (stepIndex, value) => {
|
||||||
});
|
const existingIndex = stepMatches.findIndex(
|
||||||
const addStepMatch = () => {
|
(match) => String(match.leftStepIndex) === String(stepIndex) && String(match.rightStepIndex) === String(stepIndex)
|
||||||
updateComparison('stepMatches', [...stepMatches, createStepMatch()]);
|
);
|
||||||
};
|
const nextMatch = {
|
||||||
const updateStepMatch = (matchIndex, field, value) => {
|
...(existingIndex >= 0 ? stepMatches[existingIndex] : {}),
|
||||||
|
id: `step-${stepIndex}`,
|
||||||
|
leftStepIndex: String(stepIndex),
|
||||||
|
rightStepIndex: String(stepIndex),
|
||||||
|
reason: value
|
||||||
|
};
|
||||||
updateComparison(
|
updateComparison(
|
||||||
'stepMatches',
|
'stepMatches',
|
||||||
stepMatches.map((match, index) => (index === matchIndex ? { ...match, [field]: value } : match))
|
existingIndex >= 0
|
||||||
|
? stepMatches.map((match, index) => (index === existingIndex ? nextMatch : match))
|
||||||
|
: [...stepMatches, nextMatch]
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
const removeStepMatch = (matchIndex) => {
|
|
||||||
updateComparison('stepMatches', stepMatches.filter((_, index) => index !== matchIndex));
|
|
||||||
};
|
|
||||||
const getStepLabel = (step, index) => `${index + 1}. ${step?.title ?? '-'}`;
|
const getStepLabel = (step, index) => `${index + 1}. ${step?.title ?? '-'}`;
|
||||||
|
|
||||||
const renderProgramSummary = (program) => {
|
const renderProgramSummary = (program) => {
|
||||||
@@ -1358,88 +1363,71 @@ function ProgramComparePopup({ programs, comparisons, onComparisonChange, onClos
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4 rounded-3xl bg-white p-4 ring-1 ring-slate-100">
|
<div className="mt-4 rounded-3xl bg-white p-4 ring-1 ring-slate-100">
|
||||||
<div className="flex items-center justify-between gap-3">
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-base font-black text-slate-950">스텝 1:1 매칭</h3>
|
<h3 className="text-base font-black text-slate-950">전체 스텝 1:1 비교</h3>
|
||||||
<p className="mt-1 text-[12px] font-bold text-slate-500">
|
<p className="mt-1 text-[12px] font-bold text-slate-500">
|
||||||
왼쪽 스텝과 오른쪽 스텝을 연결하고, 가운데에 상용 프로그램을 쓰는 이유를 적습니다.
|
두 프로그램의 스텝을 전부 펼친 뒤, 가운데에 해당 스텝에서 상용 프로그램을 쓰는 이유를 적습니다.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={addStepMatch}
|
|
||||||
className="flex shrink-0 items-center gap-1.5 rounded-full bg-blue-600 px-3.5 py-2 text-[12px] font-black text-white shadow-sm hover:bg-blue-700"
|
|
||||||
>
|
|
||||||
<Plus className="h-3.5 w-3.5" />
|
|
||||||
매칭 추가
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-4 space-y-3">
|
<div className="mt-4 space-y-3">
|
||||||
{stepMatches.length === 0 ? (
|
{maxStepCount === 0 ? (
|
||||||
<div className="rounded-2xl bg-slate-50 px-4 py-6 text-center text-sm font-bold text-slate-500 ring-1 ring-slate-100">
|
<div className="rounded-2xl bg-slate-50 px-4 py-6 text-center text-sm font-bold text-slate-500 ring-1 ring-slate-100">
|
||||||
아직 매칭된 스텝이 없습니다. `매칭 추가`를 눌러 스텝을 연결하세요.
|
비교할 스텝이 없습니다.
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
stepMatches.map((match, matchIndex) => (
|
Array.from({ length: maxStepCount }).map((_, stepIndex) => {
|
||||||
|
const leftStep = leftSteps[stepIndex];
|
||||||
|
const rightStep = rightSteps[stepIndex];
|
||||||
|
const match = getStepMatch(stepIndex);
|
||||||
|
return (
|
||||||
<div
|
<div
|
||||||
key={match.id ?? matchIndex}
|
key={`step-compare-${stepIndex}`}
|
||||||
className="grid gap-3 rounded-2xl border border-slate-100 bg-slate-50/80 p-3 lg:grid-cols-[minmax(0,1fr)_minmax(260px,1.35fr)_minmax(0,1fr)_40px]"
|
className="grid gap-3 rounded-2xl border border-slate-100 bg-slate-50/80 p-3 lg:grid-cols-[minmax(0,1fr)_minmax(280px,1.35fr)_minmax(0,1fr)]"
|
||||||
>
|
>
|
||||||
<label className="block">
|
<div className="rounded-2xl bg-amber-50/70 p-3 ring-1 ring-amber-100">
|
||||||
<span className="text-[11px] font-black text-amber-700">{leftProgram?.name ?? 'A'} 스텝</span>
|
<span className="text-[11px] font-black text-amber-700">{leftProgram?.name ?? 'A'} 스텝</span>
|
||||||
<select
|
<h4 className="mt-1 text-sm font-black text-slate-950">
|
||||||
value={match.leftStepIndex ?? '0'}
|
{leftStep ? getStepLabel(leftStep, stepIndex) : `${stepIndex + 1}. -`}
|
||||||
onChange={(event) => updateStepMatch(matchIndex, 'leftStepIndex', event.target.value)}
|
</h4>
|
||||||
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"
|
<p className="mt-2 whitespace-pre-line rounded-xl bg-white/75 px-3 py-2 text-[12px] font-bold leading-5 text-slate-700 ring-1 ring-amber-50">
|
||||||
>
|
{leftStep?.feature || '해당 스텝 없음'}
|
||||||
{leftSteps.map((step, index) => (
|
|
||||||
<option key={`${step.id ?? step.title}-${index}`} value={String(index)}>
|
|
||||||
{getStepLabel(step, index)}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
<p className="mt-2 whitespace-pre-line rounded-xl bg-white/70 px-3 py-2 text-[12px] font-bold leading-5 text-slate-600 ring-1 ring-amber-50">
|
|
||||||
{leftSteps[Number(match.leftStepIndex ?? 0)]?.feature || '-'}
|
|
||||||
</p>
|
</p>
|
||||||
</label>
|
{leftStep?.note && (
|
||||||
|
<p className="mt-2 whitespace-pre-line text-[11px] font-bold leading-5 text-slate-500">
|
||||||
|
{leftStep.note}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<label className="block">
|
<label className="block">
|
||||||
<span className="text-[11px] font-black text-slate-700">안 되는 기능 / 상용 사용 이유</span>
|
<span className="text-[11px] font-black text-slate-700">안 되는 기능 / 상용 사용 이유</span>
|
||||||
<textarea
|
<textarea
|
||||||
value={match.reason ?? ''}
|
value={match.reason ?? ''}
|
||||||
onChange={(event) => updateStepMatch(matchIndex, 'reason', event.target.value)}
|
onChange={(event) => updateStepReason(stepIndex, event.target.value)}
|
||||||
rows={5}
|
rows={5}
|
||||||
placeholder="예: 이 스텝에서 3D 형상 수정과 obj/ifc 변환이 안 돼서 Rhino의 모델 편집/포맷 변환 기능을 사용"
|
placeholder="예: 이 스텝에서 3D 형상 수정과 obj/ifc 변환이 안 돼서 오른쪽 프로그램의 기능을 사용"
|
||||||
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"
|
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"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label className="block">
|
<div className="rounded-2xl bg-blue-50/70 p-3 ring-1 ring-blue-100">
|
||||||
<span className="text-[11px] font-black text-blue-700">{rightProgram?.name ?? 'B'} 스텝</span>
|
<span className="text-[11px] font-black text-blue-700">{rightProgram?.name ?? 'B'} 스텝</span>
|
||||||
<select
|
<h4 className="mt-1 text-sm font-black text-slate-950">
|
||||||
value={match.rightStepIndex ?? '0'}
|
{rightStep ? getStepLabel(rightStep, stepIndex) : `${stepIndex + 1}. -`}
|
||||||
onChange={(event) => updateStepMatch(matchIndex, 'rightStepIndex', event.target.value)}
|
</h4>
|
||||||
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"
|
<p className="mt-2 whitespace-pre-line rounded-xl bg-white/75 px-3 py-2 text-[12px] font-bold leading-5 text-slate-700 ring-1 ring-blue-50">
|
||||||
>
|
{rightStep?.feature || '해당 스텝 없음'}
|
||||||
{rightSteps.map((step, index) => (
|
|
||||||
<option key={`${step.id ?? step.title}-${index}`} value={String(index)}>
|
|
||||||
{getStepLabel(step, index)}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
<p className="mt-2 whitespace-pre-line rounded-xl bg-white/70 px-3 py-2 text-[12px] font-bold leading-5 text-slate-600 ring-1 ring-blue-50">
|
|
||||||
{rightSteps[Number(match.rightStepIndex ?? 0)]?.feature || '-'}
|
|
||||||
</p>
|
</p>
|
||||||
</label>
|
{rightStep?.note && (
|
||||||
<button
|
<p className="mt-2 whitespace-pre-line text-[11px] font-bold leading-5 text-slate-500">
|
||||||
type="button"
|
{rightStep.note}
|
||||||
onClick={() => removeStepMatch(matchIndex)}
|
</p>
|
||||||
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="스텝 매칭 삭제"
|
</div>
|
||||||
>
|
|
||||||
<Trash2 className="h-4 w-4" />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
))
|
);
|
||||||
|
})
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user