Refine program metadata layout
This commit is contained in:
226
src/App.jsx
226
src/App.jsx
@@ -10,7 +10,6 @@ import {
|
|||||||
Layers3,
|
Layers3,
|
||||||
Map,
|
Map,
|
||||||
Maximize2,
|
Maximize2,
|
||||||
Minimize2,
|
|
||||||
Mountain,
|
Mountain,
|
||||||
Plus,
|
Plus,
|
||||||
Route,
|
Route,
|
||||||
@@ -222,6 +221,7 @@ const defaultContent = {
|
|||||||
steps: cheonjiinFlow.map(({ title, feature, note }) => ({ title, feature, note })),
|
steps: cheonjiinFlow.map(({ title, feature, note }) => ({ title, feature, note })),
|
||||||
deliverables: cheonjiinDeliverables,
|
deliverables: cheonjiinDeliverables,
|
||||||
format: 'glb',
|
format: 'glb',
|
||||||
|
engine: '',
|
||||||
programType: 'internal',
|
programType: 'internal',
|
||||||
programNote: '',
|
programNote: '',
|
||||||
predecessors: [],
|
predecessors: [],
|
||||||
@@ -233,6 +233,7 @@ const defaultContent = {
|
|||||||
description: '설계조건 입력부터 기본설계 성과물 생성',
|
description: '설계조건 입력부터 기본설계 성과물 생성',
|
||||||
steps: wayPrimalFlow.map(({ title, feature, note }) => ({ title, feature, note })),
|
steps: wayPrimalFlow.map(({ title, feature, note }) => ({ title, feature, note })),
|
||||||
format: '',
|
format: '',
|
||||||
|
engine: '',
|
||||||
deliverables: ['기본설계 모델'],
|
deliverables: ['기본설계 모델'],
|
||||||
programType: 'internal',
|
programType: 'internal',
|
||||||
programNote: '',
|
programNote: '',
|
||||||
@@ -253,6 +254,7 @@ function normalizeStoredContent(parsed) {
|
|||||||
...(parsed.cheonjiin ?? {}),
|
...(parsed.cheonjiin ?? {}),
|
||||||
programType: getProgramType(parsed.cheonjiin?.programType ?? defaultContent.cheonjiin.programType),
|
programType: getProgramType(parsed.cheonjiin?.programType ?? defaultContent.cheonjiin.programType),
|
||||||
programNote: parsed.cheonjiin?.programNote ?? '',
|
programNote: parsed.cheonjiin?.programNote ?? '',
|
||||||
|
engine: parsed.cheonjiin?.engine ?? '',
|
||||||
predecessors: parsed.cheonjiin?.predecessors ?? [],
|
predecessors: parsed.cheonjiin?.predecessors ?? [],
|
||||||
successors: parsed.cheonjiin?.successors ?? [],
|
successors: parsed.cheonjiin?.successors ?? [],
|
||||||
mergeGroup: parsed.cheonjiin?.mergeGroup ?? '',
|
mergeGroup: parsed.cheonjiin?.mergeGroup ?? '',
|
||||||
@@ -266,6 +268,7 @@ function normalizeStoredContent(parsed) {
|
|||||||
...(parsed.wayPrimal ?? {}),
|
...(parsed.wayPrimal ?? {}),
|
||||||
programType: getProgramType(parsed.wayPrimal?.programType ?? defaultContent.wayPrimal.programType),
|
programType: getProgramType(parsed.wayPrimal?.programType ?? defaultContent.wayPrimal.programType),
|
||||||
programNote: parsed.wayPrimal?.programNote ?? '',
|
programNote: parsed.wayPrimal?.programNote ?? '',
|
||||||
|
engine: parsed.wayPrimal?.engine ?? '',
|
||||||
predecessors: parsed.wayPrimal?.predecessors ?? [],
|
predecessors: parsed.wayPrimal?.predecessors ?? [],
|
||||||
successors: parsed.wayPrimal?.successors ?? [],
|
successors: parsed.wayPrimal?.successors ?? [],
|
||||||
mergeGroup: parsed.wayPrimal?.mergeGroup ?? '',
|
mergeGroup: parsed.wayPrimal?.mergeGroup ?? '',
|
||||||
@@ -283,6 +286,7 @@ function normalizeStoredContent(parsed) {
|
|||||||
...program,
|
...program,
|
||||||
programType: getProgramType(program.programType),
|
programType: getProgramType(program.programType),
|
||||||
programNote: program.programNote ?? '',
|
programNote: program.programNote ?? '',
|
||||||
|
engine: program.engine ?? '',
|
||||||
predecessors: program.predecessors ?? [],
|
predecessors: program.predecessors ?? [],
|
||||||
successors: program.successors ?? [],
|
successors: program.successors ?? [],
|
||||||
mergeGroup: program.mergeGroup ?? '',
|
mergeGroup: program.mergeGroup ?? '',
|
||||||
@@ -568,6 +572,8 @@ function FlowRow({
|
|||||||
onStepChange,
|
onStepChange,
|
||||||
format,
|
format,
|
||||||
onFormatChange,
|
onFormatChange,
|
||||||
|
engine = '',
|
||||||
|
onEngineChange,
|
||||||
deliverables = [],
|
deliverables = [],
|
||||||
onDeliverableChange,
|
onDeliverableChange,
|
||||||
onLabelChange,
|
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)]' : ''
|
isRowClickable ? 'cursor-pointer transition hover:-translate-y-0.5 hover:shadow-[0_22px_60px_rgba(15,23,42,0.12)]' : ''
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="mb-5 flex flex-col gap-2 md:flex-row md:items-end md:justify-between">
|
<div
|
||||||
<div>
|
className="mb-5 flex flex-col gap-2 md:grid md:items-start md:justify-start md:gap-5"
|
||||||
|
style={{ gridTemplateColumns: '720px minmax(0, 1fr)' }}
|
||||||
|
>
|
||||||
|
<div className="max-w-none">
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
{isEditing && onLabelChange ? (
|
{isEditing && onLabelChange ? (
|
||||||
<input
|
<input
|
||||||
@@ -740,7 +749,7 @@ function FlowRow({
|
|||||||
value={description}
|
value={description}
|
||||||
onChange={(event) => onDescriptionChange(event.target.value)}
|
onChange={(event) => onDescriptionChange(event.target.value)}
|
||||||
rows={2}
|
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"
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<h2 className="mt-1 whitespace-pre-line text-sm font-bold tracking-tight text-slate-800">{description}</h2>
|
<h2 className="mt-1 whitespace-pre-line text-sm font-bold tracking-tight text-slate-800">{description}</h2>
|
||||||
@@ -752,54 +761,77 @@ function FlowRow({
|
|||||||
onChange={(event) => onProgramNoteChange(event.target.value)}
|
onChange={(event) => onProgramNoteChange(event.target.value)}
|
||||||
rows={2}
|
rows={2}
|
||||||
placeholder="프로그램 노트를 입력하세요"
|
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 ? (
|
) : programNote ? (
|
||||||
<div className="mt-2 max-w-3xl rounded-2xl bg-amber-50/80 px-3 py-2 text-sm font-semibold leading-5 text-amber-900 ring-1 ring-amber-100">
|
<div className="mt-2 rounded-2xl bg-amber-50/80 px-3 py-2 text-sm font-semibold leading-5 text-amber-900 ring-1 ring-amber-100">
|
||||||
<span className="mr-2 text-[11px] font-black uppercase tracking-wide text-amber-600">NOTE</span>
|
<span className="mr-2 text-[11px] font-black uppercase tracking-wide text-amber-600">NOTE</span>
|
||||||
<span className="whitespace-pre-line">{programNote}</span>
|
<span className="whitespace-pre-line">{programNote}</span>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col items-start gap-2 md:items-end">
|
<div className="grid w-full gap-1.5 md:pt-1">
|
||||||
{onFormatChange && (
|
{onFormatChange && (
|
||||||
<div className="flex items-center gap-2">
|
<div className="grid w-full grid-cols-[62px_minmax(0,1fr)] items-center gap-2">
|
||||||
<span className="text-[11px] font-extrabold text-slate-400">포맷</span>
|
<span className="text-left text-[11px] font-extrabold text-slate-400">포맷</span>
|
||||||
{isEditing ? (
|
<div className="flex flex-wrap justify-start gap-1.5">
|
||||||
<input
|
{isEditing ? (
|
||||||
value={format}
|
<input
|
||||||
onChange={(event) => onFormatChange(event.target.value)}
|
value={format}
|
||||||
placeholder="예: glb"
|
onChange={(event) => onFormatChange(event.target.value)}
|
||||||
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"
|
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"
|
||||||
) : (
|
/>
|
||||||
<span className="min-w-16 rounded-full bg-slate-100 px-3 py-1.5 text-center text-[12px] font-extrabold text-slate-700">
|
) : (
|
||||||
{format || '-'}
|
<span className="min-w-16 rounded-full bg-slate-100 px-3 py-1.5 text-center text-[12px] font-extrabold text-slate-700">
|
||||||
</span>
|
{format || '-'}
|
||||||
)}
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{(deliverables.length > 0 || isEditing) && (
|
{(deliverables.length > 0 || isEditing) && (
|
||||||
<div className="flex max-w-[720px] flex-wrap items-center justify-start gap-1.5 md:justify-end">
|
<div className="grid w-full grid-cols-[62px_minmax(0,1fr)] items-start gap-2">
|
||||||
<span className={`mr-1 text-[11px] font-extrabold ${accent.labelText}`}>성과물</span>
|
<span className={`pt-1 text-left text-[11px] font-extrabold ${accent.labelText}`}>성과물</span>
|
||||||
{(deliverables.length > 0 ? deliverables : ['']).map((item, index) => (
|
<div className="flex flex-wrap justify-start gap-1.5">
|
||||||
isEditing ? (
|
{(deliverables.length > 0 ? deliverables : ['']).map((item, index) => (
|
||||||
|
isEditing ? (
|
||||||
|
<input
|
||||||
|
key={index}
|
||||||
|
value={item}
|
||||||
|
onChange={(event) => 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) => (
|
||||||
|
<span
|
||||||
|
key={`${deliverable}-${index}-${deliverableIndex}`}
|
||||||
|
className="rounded-full bg-slate-100 px-2.5 py-1 text-[11px] font-extrabold text-slate-700"
|
||||||
|
>
|
||||||
|
{deliverable}
|
||||||
|
</span>
|
||||||
|
))
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{onEngineChange && (
|
||||||
|
<div className="grid w-full grid-cols-[62px_minmax(0,1fr)] items-center gap-2">
|
||||||
|
<span className="text-left text-[11px] font-extrabold text-slate-400">사용엔진</span>
|
||||||
|
<div className="flex flex-wrap justify-start gap-1.5">
|
||||||
|
{isEditing ? (
|
||||||
<input
|
<input
|
||||||
key={index}
|
value={engine}
|
||||||
value={item}
|
onChange={(event) => onEngineChange(event.target.value)}
|
||||||
onChange={(event) => onDeliverableChange?.(index, event.target.value)}
|
placeholder="예: hmeg, gsim"
|
||||||
placeholder="성과물"
|
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"
|
||||||
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) => (
|
) : (
|
||||||
<span
|
<span className="min-w-16 rounded-full bg-slate-100 px-3 py-1.5 text-center text-[12px] font-extrabold text-slate-700">
|
||||||
key={`${deliverable}-${index}-${deliverableIndex}`}
|
{engine || '-'}
|
||||||
className="rounded-full bg-slate-100 px-2.5 py-1 text-[11px] font-extrabold text-slate-700"
|
</span>
|
||||||
>
|
)}
|
||||||
{deliverable}
|
</div>
|
||||||
</span>
|
|
||||||
))
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{isEditing && onAddStep && (
|
{isEditing && onAddStep && (
|
||||||
@@ -1597,16 +1629,30 @@ function ProgramComparePopup({ programs, comparisons, initialPair, onComparisonS
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-2 text-sm font-bold leading-5 text-slate-700">{program.description}</p>
|
<p className="mt-2 text-sm font-bold leading-5 text-slate-700">{program.description}</p>
|
||||||
<div className="mt-3 flex flex-wrap gap-1.5">
|
<div className="mt-3 grid gap-2 text-[12px] font-extrabold text-slate-500">
|
||||||
{(program.deliverables ?? []).map((deliverable) => (
|
<div className="grid grid-cols-[64px_minmax(0,1fr)] items-start gap-2">
|
||||||
<span key={deliverable} className="rounded-full bg-white/80 px-2.5 py-1 text-[11px] font-black text-slate-600 ring-1 ring-white">
|
<span className="text-right text-slate-400">포맷</span>
|
||||||
{deliverable}
|
<span className="text-slate-800">{program.format || '-'}</span>
|
||||||
</span>
|
</div>
|
||||||
))}
|
<div className="grid grid-cols-[64px_minmax(0,1fr)] items-start gap-2">
|
||||||
|
<span className="text-right text-slate-400">성과물</span>
|
||||||
|
<div className="flex flex-wrap gap-1.5">
|
||||||
|
{(program.deliverables ?? []).length > 0 ? (
|
||||||
|
(program.deliverables ?? []).map((deliverable) => (
|
||||||
|
<span key={deliverable} className="rounded-full bg-white/80 px-2.5 py-1 text-[11px] font-black text-slate-600 ring-1 ring-white">
|
||||||
|
{deliverable}
|
||||||
|
</span>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<span className="text-slate-800">-</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-[64px_minmax(0,1fr)] items-start gap-2">
|
||||||
|
<span className="text-right text-slate-400">사용엔진</span>
|
||||||
|
<span className="text-slate-800">{program.engine || '-'}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-3 text-[12px] font-extrabold text-slate-500">
|
|
||||||
포맷: <span className="text-slate-800">{program.format || '-'}</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -2083,8 +2129,7 @@ function RelationTreePanel({
|
|||||||
const miniRowGap = fullPage ? 96 : 72;
|
const miniRowGap = fullPage ? 96 : 72;
|
||||||
const miniPadding = 34;
|
const miniPadding = 34;
|
||||||
const miniSideLaneWidth = 96;
|
const miniSideLaneWidth = 96;
|
||||||
const sidebarSizes = [420, 560, 720];
|
const resolvedSidebarWidth = sidebarWidth ?? 420;
|
||||||
const resolvedSidebarWidth = sidebarWidth ?? sidebarSizes[0];
|
|
||||||
const miniViewportWidth = Math.max(290, fullPage ? viewportSize.width - 64 : resolvedSidebarWidth - 24);
|
const miniViewportWidth = Math.max(290, fullPage ? viewportSize.width - 64 : resolvedSidebarWidth - 24);
|
||||||
const miniMaxLevelCount = Math.max(1, ...graphLevels.map((level) => level?.length ?? 0));
|
const miniMaxLevelCount = Math.max(1, ...graphLevels.map((level) => level?.length ?? 0));
|
||||||
const miniGraphWidth = Math.max(
|
const miniGraphWidth = Math.max(
|
||||||
@@ -2138,11 +2183,6 @@ function RelationTreePanel({
|
|||||||
const miniScaledWidth = miniGraphWidth * miniGraphScale;
|
const miniScaledWidth = miniGraphWidth * miniGraphScale;
|
||||||
const mapOffset = fullPage ? 0 : Math.max(0, Math.round((miniScaledWidth - miniViewportWidth) / 2 / 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 mapTranslateX = fullPage ? Math.max(0, Math.round((miniViewportWidth - miniScaledWidth) / 2)) : -mapOffset;
|
||||||
const canShrinkSidebar = resolvedSidebarWidth > sidebarSizes[0];
|
|
||||||
const setSidebarSize = (size) => {
|
|
||||||
onSidebarWidthChange?.(size);
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!fullPage) return undefined;
|
if (!fullPage) return undefined;
|
||||||
const updateViewportSize = () => {
|
const updateViewportSize = () => {
|
||||||
@@ -2332,29 +2372,19 @@ function RelationTreePanel({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{!fullPage && (
|
{!fullPage && (
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
<div className="flex shrink-0 items-center gap-1">
|
<div className="flex shrink-0 items-center gap-1">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
disabled={!canShrinkSidebar}
|
onClick={onOpenMapWindow}
|
||||||
onClick={() => setSidebarSize(sidebarSizes[0])}
|
className="flex h-7 w-7 items-center justify-center rounded-full bg-white text-slate-500 shadow-sm ring-1 ring-slate-200 hover:bg-slate-50"
|
||||||
className="flex h-7 w-7 items-center justify-center rounded-full bg-white text-slate-500 shadow-sm ring-1 ring-slate-200 hover:bg-slate-50 disabled:opacity-25"
|
aria-label="연결도 새창으로 보기"
|
||||||
aria-label="연결도 영역 최소"
|
title="연결도 새창으로 보기"
|
||||||
title="연결도 영역 최소"
|
>
|
||||||
>
|
<Maximize2 className="h-3.5 w-3.5" />
|
||||||
<Minimize2 className="h-3.5 w-3.5" />
|
</button>
|
||||||
</button>
|
</div>
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={onOpenMapWindow}
|
|
||||||
className="flex h-7 w-7 items-center justify-center rounded-full bg-white text-slate-500 shadow-sm ring-1 ring-slate-200 hover:bg-slate-50 disabled:opacity-25"
|
|
||||||
aria-label="연결도 새창으로 보기"
|
|
||||||
title="연결도 새창으로 보기"
|
|
||||||
>
|
|
||||||
<Maximize2 className="h-3.5 w-3.5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{relations.length > 0 ? (
|
{relations.length > 0 ? (
|
||||||
@@ -2655,6 +2685,7 @@ export default function App() {
|
|||||||
programNote: content.cheonjiin.programNote ?? '',
|
programNote: content.cheonjiin.programNote ?? '',
|
||||||
steps: editableCheonjiinFlow,
|
steps: editableCheonjiinFlow,
|
||||||
format: content.cheonjiin.format,
|
format: content.cheonjiin.format,
|
||||||
|
engine: content.cheonjiin.engine ?? '',
|
||||||
programType: getProgramType(content.cheonjiin.programType),
|
programType: getProgramType(content.cheonjiin.programType),
|
||||||
predecessors: content.cheonjiin.predecessors ?? [],
|
predecessors: content.cheonjiin.predecessors ?? [],
|
||||||
successors: content.cheonjiin.successors ?? [],
|
successors: content.cheonjiin.successors ?? [],
|
||||||
@@ -2673,6 +2704,7 @@ export default function App() {
|
|||||||
programNote: content.wayPrimal.programNote ?? '',
|
programNote: content.wayPrimal.programNote ?? '',
|
||||||
steps: editableWayPrimalFlow,
|
steps: editableWayPrimalFlow,
|
||||||
format: content.wayPrimal.format,
|
format: content.wayPrimal.format,
|
||||||
|
engine: content.wayPrimal.engine ?? '',
|
||||||
programType: getProgramType(content.wayPrimal.programType),
|
programType: getProgramType(content.wayPrimal.programType),
|
||||||
predecessors: content.wayPrimal.predecessors ?? [],
|
predecessors: content.wayPrimal.predecessors ?? [],
|
||||||
successors: content.wayPrimal.successors ?? [],
|
successors: content.wayPrimal.successors ?? [],
|
||||||
@@ -2691,6 +2723,7 @@ export default function App() {
|
|||||||
programNote: program.programNote ?? '',
|
programNote: program.programNote ?? '',
|
||||||
steps: normalizeProgramSteps(program),
|
steps: normalizeProgramSteps(program),
|
||||||
format: program.format,
|
format: program.format,
|
||||||
|
engine: program.engine ?? '',
|
||||||
programType: getProgramType(program.programType),
|
programType: getProgramType(program.programType),
|
||||||
predecessors: program.predecessors ?? [],
|
predecessors: program.predecessors ?? [],
|
||||||
successors: program.successors ?? [],
|
successors: program.successors ?? [],
|
||||||
@@ -3455,6 +3488,7 @@ export default function App() {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
format: '',
|
format: '',
|
||||||
|
engine: '',
|
||||||
deliverables: ['성과물'],
|
deliverables: ['성과물'],
|
||||||
programType: 'internal',
|
programType: 'internal',
|
||||||
programNote: '',
|
programNote: '',
|
||||||
@@ -3709,7 +3743,17 @@ export default function App() {
|
|||||||
return (
|
return (
|
||||||
<main className="min-h-screen bg-white text-slate-900">
|
<main className="min-h-screen bg-white text-slate-900">
|
||||||
<div className="mx-auto max-w-[1760px] space-y-4 px-3 py-4">
|
<div className="mx-auto max-w-[1760px] space-y-4 px-3 py-4">
|
||||||
<div className="flex justify-end gap-2">
|
<div className="sticky top-0 z-50 -mx-3 flex justify-end gap-2 border-b border-slate-100 bg-white/95 px-3 py-3 backdrop-blur">
|
||||||
|
{isEditing && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={addProgram}
|
||||||
|
className="flex items-center gap-2 rounded-full bg-blue-600 px-4 py-2 text-sm font-bold text-white shadow-sm hover:bg-blue-700"
|
||||||
|
>
|
||||||
|
<Plus className="h-4 w-4" />
|
||||||
|
프로그램 추가
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={toggleProgramEdit}
|
onClick={toggleProgramEdit}
|
||||||
@@ -3748,26 +3792,6 @@ export default function App() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<section className="min-w-0 space-y-5">
|
<section className="min-w-0 space-y-5">
|
||||||
{isEditing && (
|
|
||||||
<div className="flex items-center justify-between rounded-[24px] border border-white/70 bg-white/70 px-5 py-4 shadow-sm backdrop-blur">
|
|
||||||
<div>
|
|
||||||
<p className="text-[12px] font-extrabold uppercase tracking-wide text-blue-700">
|
|
||||||
프로그램 편집
|
|
||||||
</p>
|
|
||||||
<h2 className="mt-1 text-lg font-extrabold text-slate-950">
|
|
||||||
프로그램을 추가하거나 기존 내용을 수정합니다
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={addProgram}
|
|
||||||
className="flex items-center gap-2 rounded-full bg-blue-600 px-4 py-2 text-sm font-bold text-white shadow-sm hover:bg-blue-700"
|
|
||||||
>
|
|
||||||
<Plus className="h-4 w-4" />
|
|
||||||
프로그램 추가
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<FlowRow
|
<FlowRow
|
||||||
label={content.cheonjiin.name}
|
label={content.cheonjiin.name}
|
||||||
@@ -3785,6 +3809,8 @@ export default function App() {
|
|||||||
onProgramTypeChange={(value) => updateProgramTitle('cheonjiin', 'programType', value)}
|
onProgramTypeChange={(value) => updateProgramTitle('cheonjiin', 'programType', value)}
|
||||||
format={content.cheonjiin.format}
|
format={content.cheonjiin.format}
|
||||||
onFormatChange={(value) => updateFormat('cheonjiin', value)}
|
onFormatChange={(value) => updateFormat('cheonjiin', value)}
|
||||||
|
engine={content.cheonjiin.engine ?? ''}
|
||||||
|
onEngineChange={(value) => updateProgramTitle('cheonjiin', 'engine', value)}
|
||||||
deliverables={content.cheonjiin.deliverables}
|
deliverables={content.cheonjiin.deliverables}
|
||||||
onDeliverableChange={(index, value) => updateProgramDeliverable('cheonjiin', index, value)}
|
onDeliverableChange={(index, value) => updateProgramDeliverable('cheonjiin', index, value)}
|
||||||
onAddStep={() => addStep('cheonjiin')}
|
onAddStep={() => addStep('cheonjiin')}
|
||||||
@@ -3819,6 +3845,8 @@ export default function App() {
|
|||||||
onProgramTypeChange={(value) => updateProgramTitle('wayPrimal', 'programType', value)}
|
onProgramTypeChange={(value) => updateProgramTitle('wayPrimal', 'programType', value)}
|
||||||
format={content.wayPrimal.format}
|
format={content.wayPrimal.format}
|
||||||
onFormatChange={(value) => updateFormat('wayPrimal', value)}
|
onFormatChange={(value) => updateFormat('wayPrimal', value)}
|
||||||
|
engine={content.wayPrimal.engine ?? ''}
|
||||||
|
onEngineChange={(value) => updateProgramTitle('wayPrimal', 'engine', value)}
|
||||||
deliverables={content.wayPrimal.deliverables ?? []}
|
deliverables={content.wayPrimal.deliverables ?? []}
|
||||||
onDeliverableChange={(index, value) => updateProgramDeliverable('wayPrimal', index, value)}
|
onDeliverableChange={(index, value) => updateProgramDeliverable('wayPrimal', index, value)}
|
||||||
onAddStep={() => addStep('wayPrimal')}
|
onAddStep={() => addStep('wayPrimal')}
|
||||||
@@ -3907,6 +3935,8 @@ export default function App() {
|
|||||||
onProgramTypeChange={(value) => updateProgram(programIndex, 'programType', value)}
|
onProgramTypeChange={(value) => updateProgram(programIndex, 'programType', value)}
|
||||||
format={program.format}
|
format={program.format}
|
||||||
onFormatChange={(value) => updateProgram(programIndex, 'format', value)}
|
onFormatChange={(value) => updateProgram(programIndex, 'format', value)}
|
||||||
|
engine={program.engine ?? ''}
|
||||||
|
onEngineChange={(value) => updateProgram(programIndex, 'engine', value)}
|
||||||
deliverables={program.deliverables ?? []}
|
deliverables={program.deliverables ?? []}
|
||||||
onDeliverableChange={(index, value) => updateProgramDeliverable(program.id, index, value)}
|
onDeliverableChange={(index, value) => updateProgramDeliverable(program.id, index, value)}
|
||||||
onAddStep={() => addStep(program.id)}
|
onAddStep={() => addStep(program.id)}
|
||||||
|
|||||||
Reference in New Issue
Block a user