EENE Dashboard upload to Gitea
@@ -42,7 +42,7 @@ const HUB_CONFIG = {
|
|||||||
|
|
||||||
],
|
],
|
||||||
|
|
||||||
routineLabels: ['채용 운영', '학습 지원', '직원 소통', '자산·시설', '문서·행정'],
|
routineLabels: ['채용 운영', '학습 지원', '운영 지원', '자산·시설', '문서·행정'],
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -13,11 +13,22 @@ export const DEFAULT_HUB_CONFIG = {
|
|||||||
{ id: '2', date: '2026-05-15', text: '조직문화 진단·리더십 교육' },
|
{ id: '2', date: '2026-05-15', text: '조직문화 진단·리더십 교육' },
|
||||||
{ id: '3', date: '2026-06-20', text: '분기 성과 점검·평가' },
|
{ id: '3', date: '2026-06-20', text: '분기 성과 점검·평가' },
|
||||||
],
|
],
|
||||||
routineLabels: ['채용 운영', '학습 지원', '직원 소통', '자산·시설', '문서·행정'],
|
routineLabels: ['채용 운영', '학습 지원', '운영 지원', '자산·시설', '문서·행정'],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function migrateRoutineLabels(labels: string[]): string[] {
|
||||||
|
return labels.map((label) => {
|
||||||
|
if (label === '교육 운영') return '학습 지원';
|
||||||
|
if (label === '직원 소통') return '운영 지원';
|
||||||
|
return label;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeConfig(raw: Record<string, unknown>) {
|
function normalizeConfig(raw: Record<string, unknown>) {
|
||||||
const sloganTitle = (raw.sloganTitle as string) ?? DEFAULT_HUB_CONFIG.sloganTitle;
|
const sloganTitle = (raw.sloganTitle as string) ?? DEFAULT_HUB_CONFIG.sloganTitle;
|
||||||
|
const routineLabels = Array.isArray(raw.routineLabels)
|
||||||
|
? migrateRoutineLabels(raw.routineLabels as string[])
|
||||||
|
: DEFAULT_HUB_CONFIG.routineLabels;
|
||||||
return {
|
return {
|
||||||
sloganTitle: sloganTitle === '분기 슬로건' ? '분기 중점 과제' : sloganTitle,
|
sloganTitle: sloganTitle === '분기 슬로건' ? '분기 중점 과제' : sloganTitle,
|
||||||
sloganLines: Array.isArray(raw.sloganLines)
|
sloganLines: Array.isArray(raw.sloganLines)
|
||||||
@@ -27,9 +38,7 @@ function normalizeConfig(raw: Record<string, unknown>) {
|
|||||||
scheduleItems: Array.isArray(raw.scheduleItems)
|
scheduleItems: Array.isArray(raw.scheduleItems)
|
||||||
? (raw.scheduleItems as typeof DEFAULT_HUB_CONFIG.scheduleItems)
|
? (raw.scheduleItems as typeof DEFAULT_HUB_CONFIG.scheduleItems)
|
||||||
: DEFAULT_HUB_CONFIG.scheduleItems,
|
: DEFAULT_HUB_CONFIG.scheduleItems,
|
||||||
routineLabels: Array.isArray(raw.routineLabels)
|
routineLabels,
|
||||||
? (raw.routineLabels as string[])
|
|
||||||
: DEFAULT_HUB_CONFIG.routineLabels,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
BIN
data/postgres/base/16384/24652_fsm
Normal file
BIN
data/postgres/base/16384/32768_fsm
Normal file
BIN
data/postgres/base/16384/32768_vm
Normal file
@@ -1645,7 +1645,7 @@
|
|||||||
<div class="hub-routine-grid">
|
<div class="hub-routine-grid">
|
||||||
<button type="button" class="hub-routine-item">채용 운영</button>
|
<button type="button" class="hub-routine-item">채용 운영</button>
|
||||||
<button type="button" class="hub-routine-item">교육 운영</button>
|
<button type="button" class="hub-routine-item">교육 운영</button>
|
||||||
<button type="button" class="hub-routine-item">직원 소통</button>
|
<button type="button" class="hub-routine-item">운영 지원</button>
|
||||||
<button type="button" class="hub-routine-item">자산·시설</button>
|
<button type="button" class="hub-routine-item">자산·시설</button>
|
||||||
<button type="button" class="hub-routine-item">문서·행정</button>
|
<button type="button" class="hub-routine-item">문서·행정</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ const DUMMY_DEPARTMENTS: DeptBlock[] = [
|
|||||||
|
|
||||||
const HUB_MESSAGE = '인사·육성·문화·총무 개선과제 정상 추진';
|
const HUB_MESSAGE = '인사·육성·문화·총무 개선과제 정상 추진';
|
||||||
|
|
||||||
const ROUTINE_ITEMS = ['채용 운영', '교육 운영', '직원 소통', '자산·시설 관리', '문서·행정 지원'];
|
const ROUTINE_ITEMS = ['채용 운영', '교육 운영', '운영 지원', '자산·시설 관리', '문서·행정 지원'];
|
||||||
|
|
||||||
const FOCUS_ITEMS = ['핵심직무 채용 완료', '복지제도 개선안 확정', '안전보안 점검 강화'];
|
const FOCUS_ITEMS = ['핵심직무 채용 완료', '복지제도 개선안 확정', '안전보안 점검 강화'];
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ const VARIANT_DEFAULTS = {
|
|||||||
portfolioTab: '분기 전체',
|
portfolioTab: '분기 전체',
|
||||||
},
|
},
|
||||||
routine: {
|
routine: {
|
||||||
focusTitle: '업무명별 타임라인',
|
focusTitle: '업무별 타임라인',
|
||||||
projectTitle: '상시 전체 일정',
|
projectTitle: '상시 전체 일정',
|
||||||
portfolioTitle: '분기 전체 상시업무',
|
portfolioTitle: '분기 전체 상시업무',
|
||||||
rowLabelHeader: '업무명',
|
rowLabelHeader: '업무명',
|
||||||
@@ -140,9 +140,45 @@ export function MilestoneTimeline({
|
|||||||
const chartRef = useRef<HTMLDivElement>(null);
|
const chartRef = useRef<HTMLDivElement>(null);
|
||||||
const labelsScrollRef = useRef<HTMLDivElement>(null);
|
const labelsScrollRef = useRef<HTMLDivElement>(null);
|
||||||
const rowRefs = useRef<Map<string, HTMLDivElement>>(new Map());
|
const rowRefs = useRef<Map<string, HTMLDivElement>>(new Map());
|
||||||
|
const measureRef = useRef<HTMLSpanElement>(null);
|
||||||
const dragRef = useRef<{ startX: number; viewport: { start: Date; end: Date } } | null>(null);
|
const dragRef = useRef<{ startX: number; viewport: { start: Date; end: Date } } | null>(null);
|
||||||
const didPanRef = useRef(false);
|
const didPanRef = useRef(false);
|
||||||
const [isPanning, setIsPanning] = useState(false);
|
const [isPanning, setIsPanning] = useState(false);
|
||||||
|
const [expandedBar, setExpandedBar] = useState<{
|
||||||
|
id: string;
|
||||||
|
widthPx: number;
|
||||||
|
leftPx: number;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
const measureTitleWidth = (title: string) => {
|
||||||
|
const el = measureRef.current;
|
||||||
|
if (!el) return 0;
|
||||||
|
el.textContent = title;
|
||||||
|
return el.offsetWidth + 20;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFocusBarEnter = (
|
||||||
|
row: { id: string; leftPct: number; widthPct: number },
|
||||||
|
title: string,
|
||||||
|
event: React.MouseEvent<HTMLButtonElement>,
|
||||||
|
) => {
|
||||||
|
const chart = chartRef.current;
|
||||||
|
if (!chart) return;
|
||||||
|
const btn = event.currentTarget;
|
||||||
|
const chartWidth = chart.clientWidth;
|
||||||
|
const leftPx = (row.leftPct / 100) * chartWidth;
|
||||||
|
const origWidthPx = btn.offsetWidth;
|
||||||
|
const neededWidthPx = measureTitleWidth(title);
|
||||||
|
if (neededWidthPx <= origWidthPx + 1) return;
|
||||||
|
|
||||||
|
const widthPx = Math.min(chartWidth, neededWidthPx);
|
||||||
|
const extra = widthPx - origWidthPx;
|
||||||
|
let expandedLeftPx = leftPx - extra / 2;
|
||||||
|
if (expandedLeftPx < 0) expandedLeftPx = 0;
|
||||||
|
if (expandedLeftPx + widthPx > chartWidth) expandedLeftPx = chartWidth - widthPx;
|
||||||
|
|
||||||
|
setExpandedBar({ id: row.id, widthPx, leftPx: expandedLeftPx });
|
||||||
|
};
|
||||||
|
|
||||||
const rowGroups = useMemo(() => {
|
const rowGroups = useMemo(() => {
|
||||||
if (!model) return [];
|
if (!model) return [];
|
||||||
@@ -286,6 +322,7 @@ export function MilestoneTimeline({
|
|||||||
setIsPanning(false);
|
setIsPanning(false);
|
||||||
dragRef.current = null;
|
dragRef.current = null;
|
||||||
didPanRef.current = false;
|
didPanRef.current = false;
|
||||||
|
setExpandedBar(null);
|
||||||
}, [viewMode]);
|
}, [viewMode]);
|
||||||
|
|
||||||
const handleRowSelect = (milestoneId: string, taskId: string) => {
|
const handleRowSelect = (milestoneId: string, taskId: string) => {
|
||||||
@@ -294,7 +331,7 @@ export function MilestoneTimeline({
|
|||||||
onSelect?.(milestoneId);
|
onSelect?.(milestoneId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderBar = (
|
const renderGanttBar = (
|
||||||
group: {
|
group: {
|
||||||
milestoneId: string;
|
milestoneId: string;
|
||||||
title: string;
|
title: string;
|
||||||
@@ -303,13 +340,12 @@ export function MilestoneTimeline({
|
|||||||
},
|
},
|
||||||
isSelected: boolean,
|
isSelected: boolean,
|
||||||
taskId: string,
|
taskId: string,
|
||||||
showGanttBar: boolean,
|
|
||||||
) =>
|
) =>
|
||||||
group.segments.map((row) => (
|
group.segments.map((row) => (
|
||||||
<button
|
<button
|
||||||
key={row.id}
|
key={row.id}
|
||||||
type="button"
|
type="button"
|
||||||
className={`milestone-timeline__bar ${isSelected ? 'is-selected' : ''} ${showGanttBar ? 'is-gantt' : ''}`}
|
className={`milestone-timeline__bar is-gantt ${isSelected ? 'is-selected' : ''}`}
|
||||||
style={{ left: `${row.leftPct}%`, width: `${row.widthPct}%` }}
|
style={{ left: `${row.leftPct}%`, width: `${row.widthPct}%` }}
|
||||||
aria-label={group.title}
|
aria-label={group.title}
|
||||||
title={group.title}
|
title={group.title}
|
||||||
@@ -317,12 +353,60 @@ export function MilestoneTimeline({
|
|||||||
>
|
>
|
||||||
<span className="milestone-timeline__bar-track" />
|
<span className="milestone-timeline__bar-track" />
|
||||||
<span className="milestone-timeline__bar-fill" style={{ width: `${row.progress}%` }} />
|
<span className="milestone-timeline__bar-fill" style={{ width: `${row.progress}%` }} />
|
||||||
{showGanttBar && group.progress > 0 && (
|
{group.progress > 0 && (
|
||||||
<span className="milestone-timeline__bar-progress">{group.progress}%</span>
|
<span className="milestone-timeline__bar-progress">{group.progress}%</span>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
));
|
));
|
||||||
|
|
||||||
|
const renderFocusBar = (
|
||||||
|
group: {
|
||||||
|
milestoneId: string;
|
||||||
|
title: string;
|
||||||
|
progress: number;
|
||||||
|
segments: Array<{ id: string; leftPct: number; widthPct: number; progress: number }>;
|
||||||
|
},
|
||||||
|
isSelected: boolean,
|
||||||
|
taskId: string,
|
||||||
|
) =>
|
||||||
|
group.segments.map((row) => {
|
||||||
|
const isExpanded = expandedBar?.id === row.id;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={row.id}
|
||||||
|
type="button"
|
||||||
|
className={`milestone-timeline__bar${isSelected ? ' is-selected' : ''}${isExpanded ? ' is-expanded' : ''}`}
|
||||||
|
style={
|
||||||
|
isExpanded
|
||||||
|
? { left: expandedBar!.leftPx, width: expandedBar!.widthPx }
|
||||||
|
: { left: `${row.leftPct}%`, width: `${row.widthPct}%` }
|
||||||
|
}
|
||||||
|
aria-label={group.title}
|
||||||
|
title={group.title}
|
||||||
|
onMouseEnter={(e) => handleFocusBarEnter(row, group.title, e)}
|
||||||
|
onMouseLeave={() => setExpandedBar(null)}
|
||||||
|
onClick={() => handleRowSelect(group.milestoneId, taskId)}
|
||||||
|
>
|
||||||
|
<span className="milestone-timeline__bar-track" />
|
||||||
|
<span className="milestone-timeline__bar-fill" style={{ width: `${row.progress}%` }} />
|
||||||
|
<span className="milestone-timeline__bar-label-wrap" aria-hidden="true">
|
||||||
|
<span
|
||||||
|
className="milestone-timeline__bar-label milestone-timeline__bar-label--fill"
|
||||||
|
style={{ clipPath: `inset(0 ${100 - row.progress}% 0 0)` }}
|
||||||
|
>
|
||||||
|
{group.title}
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
className="milestone-timeline__bar-label milestone-timeline__bar-label--track"
|
||||||
|
style={{ clipPath: `inset(0 0 0 ${row.progress}%)` }}
|
||||||
|
>
|
||||||
|
{group.title}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
const renderTicks = () => (
|
const renderTicks = () => (
|
||||||
<div className="milestone-timeline__ticks" aria-hidden="true">
|
<div className="milestone-timeline__ticks" aria-hidden="true">
|
||||||
{model!.ticks.map((tick) => (
|
{model!.ticks.map((tick) => (
|
||||||
@@ -362,15 +446,12 @@ export function MilestoneTimeline({
|
|||||||
const renderChartHead = () => (
|
const renderChartHead = () => (
|
||||||
<div className="milestone-timeline__chart-head">
|
<div className="milestone-timeline__chart-head">
|
||||||
{renderTicks()}
|
{renderTicks()}
|
||||||
<span className="milestone-timeline__viewport-range">
|
|
||||||
{model!.rangeStartLabel}~{model!.rangeEndLabel}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderPanHint = () => (
|
const renderPanHint = () => (
|
||||||
<p className="milestone-timeline__pan-hint">
|
<p className="milestone-timeline__pan-hint">
|
||||||
드래그: 기간 이동 · 휠: 확대/축소 · {model!.rangeStartLabel}~{model!.rangeEndLabel}
|
드래그: 기간 이동 · 휠: 확대/축소
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -425,11 +506,6 @@ export function MilestoneTimeline({
|
|||||||
<div className="milestone-timeline__body milestone-timeline__body--ticks-only">
|
<div className="milestone-timeline__body milestone-timeline__body--ticks-only">
|
||||||
{renderTicks()}
|
{renderTicks()}
|
||||||
</div>
|
</div>
|
||||||
{model && (
|
|
||||||
<span className="milestone-timeline__viewport-range">
|
|
||||||
{model.rangeStartLabel}~{model.rangeEndLabel}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -478,11 +554,10 @@ export function MilestoneTimeline({
|
|||||||
key={row.milestoneId}
|
key={row.milestoneId}
|
||||||
className={`milestone-timeline__row${row.isCurrentTask ? ' is-current-task' : ' is-other-task'}`}
|
className={`milestone-timeline__row${row.isCurrentTask ? ' is-current-task' : ' is-other-task'}`}
|
||||||
>
|
>
|
||||||
{renderBar(
|
{renderGanttBar(
|
||||||
row,
|
row,
|
||||||
row.milestoneId === selectedId && row.isCurrentTask,
|
row.milestoneId === selectedId && row.isCurrentTask,
|
||||||
row.taskId,
|
row.taskId,
|
||||||
true,
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
@@ -530,7 +605,7 @@ export function MilestoneTimeline({
|
|||||||
else rowRefs.current.delete(group.milestoneId);
|
else rowRefs.current.delete(group.milestoneId);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{renderBar(group, group.milestoneId === selectedId, ownerTaskId ?? '', true)}
|
{renderGanttBar(group, group.milestoneId === selectedId, ownerTaskId ?? '')}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -541,6 +616,7 @@ export function MilestoneTimeline({
|
|||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="milestone-timeline__focus-layout">
|
<div className="milestone-timeline__focus-layout">
|
||||||
|
<span ref={measureRef} className="milestone-timeline__measure" aria-hidden="true" />
|
||||||
{renderChartHead()}
|
{renderChartHead()}
|
||||||
<div
|
<div
|
||||||
className={chartPannableClass}
|
className={chartPannableClass}
|
||||||
@@ -551,7 +627,7 @@ export function MilestoneTimeline({
|
|||||||
<div className="milestone-timeline__rows">
|
<div className="milestone-timeline__rows">
|
||||||
{projectRowGroups.map((group) => (
|
{projectRowGroups.map((group) => (
|
||||||
<div key={group.milestoneId} className="milestone-timeline__row">
|
<div key={group.milestoneId} className="milestone-timeline__row">
|
||||||
{renderBar(group, group.milestoneId === selectedId, ownerTaskId ?? '', false)}
|
{renderFocusBar(group, group.milestoneId === selectedId, ownerTaskId ?? '')}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export const DEFAULT_HUB_CONFIG: HubConfig = {
|
|||||||
{ id: '2', date: '2026-05-15', text: '조직문화 진단·리더십 교육' },
|
{ id: '2', date: '2026-05-15', text: '조직문화 진단·리더십 교육' },
|
||||||
{ id: '3', date: '2026-06-20', text: '분기 성과 점검·평가' },
|
{ id: '3', date: '2026-06-20', text: '분기 성과 점검·평가' },
|
||||||
],
|
],
|
||||||
routineLabels: ['채용 운영', '학습 지원', '직원 소통', '자산·시설', '문서·행정'],
|
routineLabels: ['채용 운영', '학습 지원', '운영 지원', '자산·시설', '문서·행정'],
|
||||||
};
|
};
|
||||||
|
|
||||||
function migrateRoutineLabels(raw: unknown): string[] {
|
function migrateRoutineLabels(raw: unknown): string[] {
|
||||||
@@ -40,7 +40,7 @@ function migrateRoutineLabels(raw: unknown): string[] {
|
|||||||
if (labels.length === ROUTINE_CATEGORIES.length && labels.every((label, i) => label === ROUTINE_CATEGORIES[i])) {
|
if (labels.length === ROUTINE_CATEGORIES.length && labels.every((label, i) => label === ROUTINE_CATEGORIES[i])) {
|
||||||
return [...ROUTINE_CATEGORIES];
|
return [...ROUTINE_CATEGORIES];
|
||||||
}
|
}
|
||||||
const legacyFull = ['채용 운영', '교육 운영', '직원 소통', '자산·시설', '문서·행정'];
|
const legacyFull = ['채용 운영', '교육 운영', '직원 소통', '운영 지원', '자산·시설', '문서·행정'];
|
||||||
if (labels.length === legacyFull.length && labels.every((label, i) => label === legacyFull[i])) {
|
if (labels.length === legacyFull.length && labels.every((label, i) => label === legacyFull[i])) {
|
||||||
return [...ROUTINE_CATEGORIES];
|
return [...ROUTINE_CATEGORIES];
|
||||||
}
|
}
|
||||||
@@ -51,6 +51,9 @@ function migrateRoutineLabels(raw: unknown): string[] {
|
|||||||
if (labels.some((label) => label === '교육 운영')) {
|
if (labels.some((label) => label === '교육 운영')) {
|
||||||
return labels.map((label) => (label === '교육 운영' ? '학습 지원' : label));
|
return labels.map((label) => (label === '교육 운영' ? '학습 지원' : label));
|
||||||
}
|
}
|
||||||
|
if (labels.some((label) => label === '직원 소통')) {
|
||||||
|
return labels.map((label) => (label === '직원 소통' ? '운영 지원' : label));
|
||||||
|
}
|
||||||
return labels.length > 0 ? labels : [...ROUTINE_CATEGORIES];
|
return labels.length > 0 ? labels : [...ROUTINE_CATEGORIES];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ export function fmtPeriodPickerLabel(
|
|||||||
if (entry.startDate && entry.dueDate) return `${fmt(entry.startDate)}~${fmt(entry.dueDate)}`;
|
if (entry.startDate && entry.dueDate) return `${fmt(entry.startDate)}~${fmt(entry.dueDate)}`;
|
||||||
if (entry.dueDate) return fmt(entry.dueDate);
|
if (entry.dueDate) return fmt(entry.dueDate);
|
||||||
if (entry.startDate) return `${fmt(entry.startDate)}~`;
|
if (entry.startDate) return `${fmt(entry.startDate)}~`;
|
||||||
|
if (!entry.startDate && !entry.dueDate) return '상시';
|
||||||
return `기간 ${index + 1}`;
|
return `기간 ${index + 1}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +134,11 @@ export function fmtMilestonePeriodSummary(
|
|||||||
): string {
|
): string {
|
||||||
const periods = parseMilestonePeriods(milestone);
|
const periods = parseMilestonePeriods(milestone);
|
||||||
if (periods.length === 0) return '';
|
if (periods.length === 0) return '';
|
||||||
if (periods.length === 1) return fmtPeriodRange(periods[0]) || '';
|
if (periods.length === 1) {
|
||||||
|
const only = periods[0];
|
||||||
|
if (!only.startDate && !only.dueDate) return '상시';
|
||||||
|
return fmtPeriodRange(only) || '상시';
|
||||||
|
}
|
||||||
const last = periods[periods.length - 1];
|
const last = periods[periods.length - 1];
|
||||||
const lastLabel = fmtPeriodRange(last);
|
const lastLabel = fmtPeriodRange(last);
|
||||||
return lastLabel ? `${lastLabel} 외 ${periods.length - 1}건` : `기간 ${periods.length}건`;
|
return lastLabel ? `${lastLabel} 외 ${periods.length - 1}건` : `기간 ${periods.length}건`;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
export const ROUTINE_CATEGORIES = [
|
export const ROUTINE_CATEGORIES = [
|
||||||
'채용 운영',
|
'채용 운영',
|
||||||
'학습 지원',
|
'학습 지원',
|
||||||
'직원 소통',
|
'운영 지원',
|
||||||
'자산·시설',
|
'자산·시설',
|
||||||
'문서·행정',
|
'문서·행정',
|
||||||
] as const;
|
] as const;
|
||||||
@@ -16,8 +16,10 @@ const LEGACY_CATEGORY_ALIASES: Record<string, RoutineCategory> = {
|
|||||||
'교육 운영': '학습 지원',
|
'교육 운영': '학습 지원',
|
||||||
'학습 지원': '학습 지원',
|
'학습 지원': '학습 지원',
|
||||||
학습: '학습 지원',
|
학습: '학습 지원',
|
||||||
소통: '직원 소통',
|
소통: '운영 지원',
|
||||||
'직원 소통': '직원 소통',
|
'직원 소통': '운영 지원',
|
||||||
|
'운영 지원': '운영 지원',
|
||||||
|
운영지원: '운영 지원',
|
||||||
시설: '자산·시설',
|
시설: '자산·시설',
|
||||||
자산: '자산·시설',
|
자산: '자산·시설',
|
||||||
'자산·시설': '자산·시설',
|
'자산·시설': '자산·시설',
|
||||||
@@ -27,7 +29,6 @@ const LEGACY_CATEGORY_ALIASES: Record<string, RoutineCategory> = {
|
|||||||
'문서·행정 지원': '문서·행정',
|
'문서·행정 지원': '문서·행정',
|
||||||
인사관리: '채용 운영',
|
인사관리: '채용 운영',
|
||||||
학습성장: '학습 지원',
|
학습성장: '학습 지원',
|
||||||
운영지원: '자산·시설',
|
|
||||||
전산관리: '문서·행정',
|
전산관리: '문서·행정',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1007,22 +1007,6 @@
|
|||||||
margin: 0 4px;
|
margin: 0 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.milestone-timeline--portfolio .milestone-timeline__viewport-range {
|
|
||||||
padding: 0 8px 2px;
|
|
||||||
color: var(--detail-text-muted);
|
|
||||||
font-size: var(--mt-font-min);
|
|
||||||
font-weight: var(--mt-font-min-weight);
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.milestone-timeline__viewport-range {
|
|
||||||
padding: 0 8px 2px;
|
|
||||||
color: var(--detail-text-muted);
|
|
||||||
font-size: var(--mt-font-min);
|
|
||||||
font-weight: var(--mt-font-min-weight);
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.milestone-timeline--portfolio .milestone-timeline__portfolio-scroll {
|
.milestone-timeline--portfolio .milestone-timeline__portfolio-scroll {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
|
|||||||
BIN
uploads/0d379816-5608-4f27-a9fa-a61d80723758.jpg
Normal file
|
After Width: | Height: | Size: 210 KiB |
BIN
uploads/147b66f8-1d6b-461b-a6b4-60098ca09921.jpg
Normal file
|
After Width: | Height: | Size: 7.7 MiB |
BIN
uploads/31d10e9b-782b-48d4-bf84-a565fc03df4e.jpg
Normal file
|
After Width: | Height: | Size: 9.2 MiB |
BIN
uploads/33fa3bd7-1868-408f-93d9-e702f4c8f782.pdf
Normal file
BIN
uploads/37bd7508-c58f-4b2c-945e-36cf1a98bf82.jpg
Normal file
|
After Width: | Height: | Size: 212 KiB |
BIN
uploads/4156e971-610e-47ce-bb6f-bc4b5838f9fc.png
Normal file
|
After Width: | Height: | Size: 381 KiB |
BIN
uploads/41b7eb84-283b-43a2-890c-a1f2ff29eb34.pdf
Normal file
BIN
uploads/4232393e-f46f-4884-9974-a29bc6c9ec9e.jpg
Normal file
|
After Width: | Height: | Size: 926 KiB |
BIN
uploads/4d36603f-98f1-4a0f-80e3-0700d1c4099d.jpg
Normal file
|
After Width: | Height: | Size: 153 KiB |
BIN
uploads/50bd4a1e-46ec-4e44-8251-8e644472a05b.jpg
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
uploads/5ac70e63-b082-484b-ba1d-51c7472576c1.jpg
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
uploads/6dfd71fa-eea6-4e90-8b24-e3d49f6865f3.jpg
Normal file
|
After Width: | Height: | Size: 7.2 MiB |
BIN
uploads/80033802-e2d0-4118-b217-a768309455cb.png
Normal file
|
After Width: | Height: | Size: 362 KiB |
BIN
uploads/871e94c7-6972-4658-a4dc-783ccc11cdbd.pdf
Normal file
BIN
uploads/9448e193-fe60-4123-80ff-9163c0b5b3dd.jpg
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
uploads/994eeb74-a906-44ea-9340-c11d7b8d8254.pdf
Normal file
BIN
uploads/a6fbdd7a-1d79-457d-956f-6489b6a95a08.pdf
Normal file
BIN
uploads/b1449d4d-9081-47c9-9899-8e95fbfd584f.jpg
Normal file
|
After Width: | Height: | Size: 8.3 MiB |
BIN
uploads/c221e025-8e46-404d-b35c-66e661b08613.png
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
uploads/c4035330-2ea1-4300-9771-ed052e789a17.jpg
Normal file
|
After Width: | Height: | Size: 7.8 MiB |
BIN
uploads/d0193746-c9f4-42f7-95fc-f0b4f027eeb5.pdf
Normal file
BIN
uploads/dba65c4f-f6bc-4a21-b9bc-7b9f1d582406.pdf
Normal file
BIN
uploads/df07def8-d2dd-47c1-ad84-9b08fe1d6b65.png
Normal file
|
After Width: | Height: | Size: 294 KiB |
BIN
uploads/e778619e-539e-4b47-8c97-f307dd89353d.pdf
Normal file
BIN
uploads/e8acb3ef-2fcf-4119-825c-bb51c33cfa82.jpg
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
uploads/e8b14eb9-c2fc-4b42-b996-fc45dc783548.png
Normal file
|
After Width: | Height: | Size: 558 KiB |
BIN
uploads/eb530874-c7e8-4f02-bce2-e23f065cfbc0.jpg
Normal file
|
After Width: | Height: | Size: 8.1 MiB |
BIN
uploads/ed3d87c7-2e62-4edc-8148-35c9096c5b20.jpg
Normal file
|
After Width: | Height: | Size: 1.5 MiB |