679 lines
26 KiB
JavaScript
679 lines
26 KiB
JavaScript
(function () {
|
|
function createProcessMapRenderer(options) {
|
|
const {
|
|
elements,
|
|
getState,
|
|
onMainClick,
|
|
onStepClick,
|
|
onCommonRootClick,
|
|
isActiveFocus = () => false,
|
|
linkWidths = { auto: 132, manual: 132 },
|
|
sharedSegment = ['전표작성', '검토', '출금']
|
|
} = options;
|
|
|
|
const {
|
|
fullContentEl,
|
|
legendEl,
|
|
teamSelectEl,
|
|
systemSelectEl
|
|
} = elements;
|
|
|
|
let systemColorMap = new Map();
|
|
let filters = { team: '', system: '' };
|
|
|
|
function parseMultiValues(text) {
|
|
return String(text || '')
|
|
.split(',')
|
|
.map((value) => value.trim())
|
|
.filter(Boolean);
|
|
}
|
|
|
|
function getSecondFlowMeta(mainStep, step) {
|
|
const { stepMeta = {} } = getState();
|
|
return stepMeta[`0|${mainStep}|${step}`] || {};
|
|
}
|
|
|
|
function collectSystemsForLegend() {
|
|
const {
|
|
mainSteps = [],
|
|
subFlow = {},
|
|
commonItems = [],
|
|
commonSubFlow = {}
|
|
} = getState();
|
|
const set = new Set();
|
|
|
|
mainSteps.forEach((mainStep) => {
|
|
(subFlow[mainStep] || []).forEach((step) => {
|
|
parseMultiValues(getSecondFlowMeta(mainStep, step).system).forEach((system) => set.add(system));
|
|
});
|
|
});
|
|
|
|
commonItems.forEach((item) => {
|
|
(commonSubFlow[item] || []).forEach((step) => {
|
|
parseMultiValues(getSecondFlowMeta(item, step).system).forEach((system) => set.add(system));
|
|
});
|
|
});
|
|
|
|
return Array.from(set).sort((a, b) => a.localeCompare(b, 'ko'));
|
|
}
|
|
|
|
function normalizeSystemName(name) {
|
|
return String(name || '').trim().toLowerCase();
|
|
}
|
|
|
|
function getSystemCategory(systemName) {
|
|
const normalized = normalizeSystemName(systemName);
|
|
if (!normalized) return 'unknown';
|
|
if (normalized.includes('미사용')) return 'unused';
|
|
if (normalized.includes('외부')) return 'external';
|
|
if (normalized.includes('내부') || normalized.includes('erp') || normalized.includes('pq')) return 'internal';
|
|
return 'other';
|
|
}
|
|
|
|
const INTERNAL_PALETTE = [
|
|
{ bg: '#8CCBF7', border: '#69AFDF', text: '#173f6f' }, // pastel dodger
|
|
{ bg: '#A7D7F9', border: '#84C0E5', text: '#173f6f' }, // pastel picton
|
|
{ bg: '#BFE2FB', border: '#98CAE9', text: '#173f6f' }, // pastel maya
|
|
{ bg: '#D3EBFC', border: '#AFCFE6', text: '#173f6f' }, // pastel uranian
|
|
{ bg: '#E9F5FE', border: '#C5DBEC', text: '#173f6f' } // pastel beau
|
|
];
|
|
|
|
const UNUSED_PALETTE = [
|
|
{ bg: '#fff1f1', border: '#e6a3a3', text: '#762222' }, // red-1
|
|
{ bg: '#fff3f3', border: '#e8acac', text: '#7a2424' }, // red-2
|
|
{ bg: '#fff5f5', border: '#e9b4b4', text: '#7f2f2f' } // rose-red
|
|
];
|
|
|
|
const EXTERNAL_PALETTE = [
|
|
{ bg: '#eaf8ef', border: '#87ca9c', text: '#11492a' }, // green-1
|
|
{ bg: '#e8f7f1', border: '#7ac8ae', text: '#0f544d' }, // green-teal
|
|
{ bg: '#eef9f4', border: '#8bcdb0', text: '#184f39' } // green-3
|
|
];
|
|
|
|
const OTHER_PALETTE = [
|
|
{ bg: '#edf3fb', border: '#9fb8d7', text: '#26384a' },
|
|
{ bg: '#f1f4fa', border: '#a9b8cd', text: '#2b3a4d' }
|
|
];
|
|
|
|
const SPECIAL_STEP_CATEGORY = new Map([
|
|
['입찰공고', 'unused'],
|
|
['외주발주의뢰', 'unused'],
|
|
['외주발주검토', 'unused']
|
|
]);
|
|
|
|
function pickColorFromCategory(category, seedName) {
|
|
let palette = OTHER_PALETTE;
|
|
if (category === 'internal') palette = INTERNAL_PALETTE;
|
|
if (category === 'unused') palette = UNUSED_PALETTE;
|
|
if (category === 'external') palette = EXTERNAL_PALETTE;
|
|
const seed = hashString(seedName || category) % palette.length;
|
|
return palette[seed];
|
|
}
|
|
|
|
function hashString(value) {
|
|
const str = String(value || '');
|
|
let hash = 0;
|
|
for (let i = 0; i < str.length; i += 1) {
|
|
hash = ((hash << 5) - hash) + str.charCodeAt(i);
|
|
hash |= 0;
|
|
}
|
|
return Math.abs(hash);
|
|
}
|
|
|
|
function makeDistinctSystemColor(systemName, index = 0) {
|
|
const category = getSystemCategory(systemName);
|
|
let palette = OTHER_PALETTE;
|
|
if (category === 'internal') palette = INTERNAL_PALETTE;
|
|
if (category === 'unused') palette = UNUSED_PALETTE;
|
|
if (category === 'external') palette = EXTERNAL_PALETTE;
|
|
const seed = (hashString(systemName) + (index * 7)) % palette.length;
|
|
return palette[seed];
|
|
}
|
|
|
|
function rebuildSystemColorMap() {
|
|
const systems = collectSystemsForLegend();
|
|
systemColorMap = new Map();
|
|
systems.forEach((system, index) => {
|
|
systemColorMap.set(system, makeDistinctSystemColor(system, index));
|
|
});
|
|
}
|
|
|
|
function getRandomLikeColorBySystem(systemName) {
|
|
if (!systemColorMap.has(systemName)) {
|
|
systemColorMap.set(systemName, makeDistinctSystemColor(systemName, systemColorMap.size));
|
|
}
|
|
return systemColorMap.get(systemName);
|
|
}
|
|
|
|
function applyChipSystemColor(chipEl, info, stepLabel = '') {
|
|
const systems = parseMultiValues(info.system);
|
|
if (!systems.length) {
|
|
const specialCategory = SPECIAL_STEP_CATEGORY.get(String(stepLabel || '').trim()) || '';
|
|
if (specialCategory) {
|
|
const color = pickColorFromCategory(specialCategory, `${specialCategory}:${stepLabel}`);
|
|
chipEl.classList.add('system-colored');
|
|
chipEl.style.setProperty('--sys-bg', color.bg);
|
|
chipEl.style.setProperty('--sys-border', color.border);
|
|
chipEl.style.setProperty('--sys-text', color.text);
|
|
return;
|
|
}
|
|
const color = pickColorFromCategory('other', `no-system:${stepLabel}`);
|
|
chipEl.classList.add('system-colored', 'no-system');
|
|
chipEl.style.setProperty('--sys-bg', color.bg);
|
|
chipEl.style.setProperty('--sys-border', color.border);
|
|
chipEl.style.setProperty('--sys-text', color.text);
|
|
return;
|
|
}
|
|
if (systems.length >= 2) {
|
|
const colorA = getRandomLikeColorBySystem(systems[0]);
|
|
const colorB = getRandomLikeColorBySystem(systems[1]);
|
|
chipEl.classList.add('system-colored', 'multi-system');
|
|
chipEl.style.setProperty('--sys-bg-a', colorA.bg);
|
|
chipEl.style.setProperty('--sys-bg-b', colorB.bg);
|
|
chipEl.style.setProperty('--sys-border-a', colorA.border);
|
|
chipEl.style.setProperty('--sys-border-b', colorB.border);
|
|
chipEl.style.setProperty('--sys-text', colorA.text || '#1f4f83');
|
|
return;
|
|
}
|
|
const color = getRandomLikeColorBySystem(systems[0]);
|
|
chipEl.classList.add('system-colored');
|
|
chipEl.style.setProperty('--sys-bg', color.bg);
|
|
chipEl.style.setProperty('--sys-border', color.border);
|
|
chipEl.style.setProperty('--sys-text', color.text);
|
|
}
|
|
|
|
function isMetaMatchedByFilter(info) {
|
|
const teams = parseMultiValues(info.team);
|
|
const systems = parseMultiValues(info.system);
|
|
const hasAnyMeta = teams.length > 0 || systems.length > 0;
|
|
if (!filters.team && !filters.system) return true;
|
|
if (!hasAnyMeta) return false;
|
|
const teamMatch = !filters.team || teams.includes(filters.team);
|
|
const systemMatch = !filters.system || systems.includes(filters.system);
|
|
return teamMatch && systemMatch;
|
|
}
|
|
|
|
function findSharedSegmentStart(list) {
|
|
for (let index = 0; index <= list.length - sharedSegment.length; index += 1) {
|
|
const matched = sharedSegment.every((value, offset) => String(list[index + offset] || '').trim() === value);
|
|
if (matched) return index;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
function subFlowLinkKey(mainStep, fromStep, toStep) {
|
|
return `${mainStep}||${fromStep}>>${toStep}`;
|
|
}
|
|
|
|
function getSubFlowLinkInfo(mainStep, fromStep, toStep) {
|
|
const { subFlowLinks = {} } = getState();
|
|
return subFlowLinks[subFlowLinkKey(mainStep, fromStep, toStep)] || { reason: '' };
|
|
}
|
|
|
|
function formatConnectorReason(reason) {
|
|
return String(reason || '').trim();
|
|
}
|
|
|
|
function renderLegend() {
|
|
if (!legendEl) return;
|
|
legendEl.innerHTML = '';
|
|
const categoryOrder = {
|
|
internal: 0,
|
|
external: 1,
|
|
unused: 2,
|
|
other: 3,
|
|
unknown: 4
|
|
};
|
|
const systems = collectSystemsForLegend().sort((a, b) => {
|
|
const catA = getSystemCategory(a);
|
|
const catB = getSystemCategory(b);
|
|
const rankA = categoryOrder[catA] ?? 99;
|
|
const rankB = categoryOrder[catB] ?? 99;
|
|
if (rankA !== rankB) return rankA - rankB;
|
|
return a.localeCompare(b, 'ko');
|
|
});
|
|
|
|
const applyLegendSystemFilter = (systemValue) => {
|
|
const nextSystem = String(systemValue || '');
|
|
if (systemSelectEl) {
|
|
const hasOption = Array.from(systemSelectEl.options || []).some((option) => option.value === nextSystem);
|
|
systemSelectEl.value = hasOption ? nextSystem : '';
|
|
systemSelectEl.dispatchEvent(new Event('change', { bubbles: true }));
|
|
return;
|
|
}
|
|
setFilters({ system: nextSystem });
|
|
render();
|
|
};
|
|
|
|
const makeItem = (label, bg, border, opts = {}) => {
|
|
const { clickable = false, selected = false, onClick = null } = opts;
|
|
const item = document.createElement('span');
|
|
item.className = 'sm-legend-item';
|
|
if (selected) item.classList.add('active-focus');
|
|
const swatch = document.createElement('span');
|
|
swatch.className = 'sm-legend-swatch';
|
|
swatch.style.background = bg;
|
|
swatch.style.borderColor = border;
|
|
const text = document.createElement('span');
|
|
text.textContent = label;
|
|
item.appendChild(swatch);
|
|
item.appendChild(text);
|
|
if (clickable && typeof onClick === 'function') {
|
|
item.style.cursor = 'pointer';
|
|
item.title = `${label} 필터 적용`;
|
|
item.setAttribute('role', 'button');
|
|
item.setAttribute('tabindex', '0');
|
|
item.addEventListener('click', onClick);
|
|
item.addEventListener('keydown', (event) => {
|
|
if (event.key !== 'Enter' && event.key !== ' ') return;
|
|
event.preventDefault();
|
|
onClick();
|
|
});
|
|
}
|
|
legendEl.appendChild(item);
|
|
};
|
|
|
|
systems.forEach((system) => {
|
|
const color = getRandomLikeColorBySystem(system);
|
|
makeItem(system, color.bg, color.border, {
|
|
clickable: true,
|
|
selected: filters.system === system,
|
|
onClick: () => applyLegendSystemFilter(system)
|
|
});
|
|
});
|
|
makeItem('시스템 미입력', '#e5e7eb', '#cbd5e1', {
|
|
clickable: true,
|
|
selected: !filters.system,
|
|
onClick: () => applyLegendSystemFilter('')
|
|
});
|
|
}
|
|
|
|
function refreshFilterOptions() {
|
|
if (!teamSelectEl || !systemSelectEl) return;
|
|
const {
|
|
mainSteps = [],
|
|
subFlow = {},
|
|
commonItems = [],
|
|
commonSubFlow = {}
|
|
} = getState();
|
|
|
|
const teamSet = new Set();
|
|
const systemSet = new Set();
|
|
|
|
mainSteps.forEach((mainStep) => {
|
|
(subFlow[mainStep] || []).forEach((step) => {
|
|
const info = getSecondFlowMeta(mainStep, step);
|
|
parseMultiValues(info.team).forEach((value) => teamSet.add(value));
|
|
parseMultiValues(info.system).forEach((value) => systemSet.add(value));
|
|
});
|
|
});
|
|
|
|
commonItems.forEach((item) => {
|
|
(commonSubFlow[item] || []).forEach((step) => {
|
|
const info = getSecondFlowMeta(item, step);
|
|
parseMultiValues(info.team).forEach((value) => teamSet.add(value));
|
|
parseMultiValues(info.system).forEach((value) => systemSet.add(value));
|
|
});
|
|
});
|
|
|
|
const previousTeam = filters.team;
|
|
const previousSystem = filters.system;
|
|
|
|
teamSelectEl.innerHTML = '<option value="">전체 팀 필터</option>';
|
|
Array.from(teamSet).sort((a, b) => a.localeCompare(b, 'ko')).forEach((value) => {
|
|
const option = document.createElement('option');
|
|
option.value = value;
|
|
option.textContent = value;
|
|
teamSelectEl.appendChild(option);
|
|
});
|
|
|
|
systemSelectEl.innerHTML = '<option value="">전체 시스템 필터</option>';
|
|
Array.from(systemSet).sort((a, b) => a.localeCompare(b, 'ko')).forEach((value) => {
|
|
const option = document.createElement('option');
|
|
option.value = value;
|
|
option.textContent = value;
|
|
systemSelectEl.appendChild(option);
|
|
});
|
|
|
|
filters.team = Array.from(teamSet).includes(previousTeam) ? previousTeam : '';
|
|
filters.system = Array.from(systemSet).includes(previousSystem) ? previousSystem : '';
|
|
teamSelectEl.value = filters.team;
|
|
systemSelectEl.value = filters.system;
|
|
}
|
|
|
|
function markFlowItem(el, kind, colIndex) {
|
|
if (!el) return el;
|
|
el.dataset.flowKind = kind;
|
|
el.dataset.flowCol = String(colIndex);
|
|
return el;
|
|
}
|
|
|
|
function markInteractiveTarget(el, action, mainStep, step = '', isCommon = false) {
|
|
if (!el) return el;
|
|
el.dataset.mapAction = action;
|
|
el.dataset.mainStep = String(mainStep || '');
|
|
if (step) el.dataset.step = String(step);
|
|
if (isCommon) el.dataset.isCommon = '1';
|
|
el.style.cursor = 'pointer';
|
|
return el;
|
|
}
|
|
|
|
const STEP_BOX_SPACING_MULTIPLIER = 1.7;
|
|
|
|
function getSegmentBaseWidth(hasPreviousStep, isManual) {
|
|
if (!hasPreviousStep) return 0;
|
|
const baseWidth = isManual ? Number(linkWidths.manual || 156) : Number(linkWidths.auto || 132);
|
|
return baseWidth * STEP_BOX_SPACING_MULTIPLIER;
|
|
}
|
|
|
|
function applyColumnAlignment() {
|
|
const segments = Array.from(fullContentEl.querySelectorAll('.sm-segment[data-flow-col]'));
|
|
if (!segments.length) return;
|
|
|
|
const maxWidthByCol = new Map();
|
|
segments.forEach((segment) => {
|
|
const colIndex = Number(segment.dataset.flowCol || '-1');
|
|
const baseWidth = Number(segment.dataset.flowWidth || '0');
|
|
if (colIndex < 0 || baseWidth <= 0) return;
|
|
const current = maxWidthByCol.get(colIndex) || 0;
|
|
if (baseWidth > current) maxWidthByCol.set(colIndex, baseWidth);
|
|
});
|
|
|
|
segments.forEach((segment) => {
|
|
const chip = segment.querySelector('.sm-chip');
|
|
const chipWidth = chip ? Math.ceil(chip.getBoundingClientRect().width) : 102;
|
|
const hasLink = segment.dataset.hasLink === '1';
|
|
const colIndex = Number(segment.dataset.flowCol || '-1');
|
|
const totalWidth = maxWidthByCol.get(colIndex) || chipWidth;
|
|
const safeTotalWidth = Math.max(totalWidth, chipWidth);
|
|
const linkWidth = hasLink ? Math.max(safeTotalWidth - chipWidth, 24) : 0;
|
|
|
|
segment.style.setProperty('--flow-col-width', `${safeTotalWidth}px`);
|
|
segment.style.setProperty('--flow-link-width', `${linkWidth}px`);
|
|
});
|
|
}
|
|
|
|
function buildLink(mainStep, fromStep, toStep, dim) {
|
|
const link = getSubFlowLinkInfo(mainStep, fromStep, toStep);
|
|
const linkWrap = document.createElement('span');
|
|
linkWrap.className = `sm-link-wrap${link.reason ? ' manual' : ''}`;
|
|
if (dim) linkWrap.classList.add('dim');
|
|
|
|
if (link.reason) {
|
|
const prefix = document.createElement('span');
|
|
prefix.className = 'sm-link-arrow-frag';
|
|
const reason = document.createElement('span');
|
|
reason.className = 'sm-link-reason';
|
|
reason.textContent = formatConnectorReason(link.reason);
|
|
reason.title = link.reason;
|
|
const suffix = document.createElement('span');
|
|
suffix.className = 'sm-link-arrow-frag';
|
|
|
|
if (dim) {
|
|
prefix.classList.add('dim');
|
|
reason.classList.add('dim');
|
|
suffix.classList.add('dim');
|
|
}
|
|
|
|
linkWrap.appendChild(prefix);
|
|
linkWrap.appendChild(reason);
|
|
linkWrap.appendChild(suffix);
|
|
} else {
|
|
const arrow = document.createElement('span');
|
|
arrow.className = 'sm-arrow';
|
|
if (dim) arrow.classList.add('dim');
|
|
linkWrap.appendChild(arrow);
|
|
}
|
|
|
|
return linkWrap;
|
|
}
|
|
|
|
function buildStepChip(mainStep, step, info, isCommon, isMatch, hasFilter) {
|
|
const chip = document.createElement('span');
|
|
chip.className = 'sm-chip';
|
|
chip.textContent = step;
|
|
chip.title = `담당팀: ${info.team || '-'} / 시스템: ${info.system || '-'}`;
|
|
applyChipSystemColor(chip, info, step);
|
|
if (isActiveFocus('step', mainStep, step, isCommon)) chip.classList.add('active-focus');
|
|
if (typeof onStepClick === 'function') {
|
|
markInteractiveTarget(chip, 'step', mainStep, step, isCommon);
|
|
}
|
|
if (hasFilter) chip.classList.add(isMatch ? 'match' : 'dim');
|
|
return chip;
|
|
}
|
|
|
|
function buildStepSegment(mainStep, step, info, isCommon, isMatch, hasFilter, previousStep = '', dimLink = false, colIndex = 0) {
|
|
const segment = document.createElement('span');
|
|
segment.className = 'sm-segment';
|
|
markFlowItem(segment, 'segment', colIndex);
|
|
segment.dataset.hasLink = previousStep ? '1' : '0';
|
|
if (previousStep) {
|
|
const linkEl = buildLink(mainStep, previousStep, step, dimLink);
|
|
if (linkEl.classList.contains('manual')) segment.classList.add('has-manual-link');
|
|
segment.dataset.flowWidth = String(getSegmentBaseWidth(true, linkEl.classList.contains('manual')));
|
|
segment.appendChild(linkEl);
|
|
} else {
|
|
segment.classList.add('is-first');
|
|
}
|
|
if (!segment.dataset.flowWidth) segment.dataset.flowWidth = '0';
|
|
segment.appendChild(buildStepChip(mainStep, step, info, isCommon, isMatch, hasFilter));
|
|
return segment;
|
|
}
|
|
|
|
function render() {
|
|
fullContentEl.innerHTML = '';
|
|
const {
|
|
mainSteps = [],
|
|
subFlow = {},
|
|
commonItems = [],
|
|
commonSubFlow = {}
|
|
} = getState();
|
|
|
|
if (!mainSteps.length) {
|
|
const empty = document.createElement('div');
|
|
empty.className = 'empty-state';
|
|
empty.textContent = '표시할 프로세스 데이터가 없습니다.';
|
|
fullContentEl.appendChild(empty);
|
|
return;
|
|
}
|
|
|
|
rebuildSystemColorMap();
|
|
renderLegend();
|
|
const hasFilter = Boolean(filters.team || filters.system);
|
|
|
|
const projectTitle = document.createElement('p');
|
|
projectTitle.className = 'sm-section-title';
|
|
projectTitle.textContent = '프로젝트';
|
|
fullContentEl.appendChild(projectTitle);
|
|
|
|
mainSteps.forEach((mainStep) => {
|
|
const row = document.createElement('div');
|
|
row.className = 'sm-row';
|
|
|
|
const main = document.createElement('div');
|
|
main.className = 'sm-main';
|
|
main.textContent = mainStep;
|
|
if (isActiveFocus('main', mainStep)) main.classList.add('active-focus');
|
|
if (typeof onMainClick === 'function') {
|
|
main.title = `${mainStep} DETAIL 열기`;
|
|
markInteractiveTarget(main, 'main', mainStep);
|
|
}
|
|
row.appendChild(main);
|
|
|
|
const sub = subFlow[mainStep] || [];
|
|
let rowHasMatch = false;
|
|
let flowColIndex = 0;
|
|
|
|
if (sub.length > 0) {
|
|
const chain = document.createElement('div');
|
|
chain.className = 'sm-chain';
|
|
const sharedStart = findSharedSegmentStart(sub);
|
|
|
|
sub.forEach((step, index) => {
|
|
if (sharedStart >= 0 && index > sharedStart && index < sharedStart + sharedSegment.length) return;
|
|
|
|
const info = getSecondFlowMeta(mainStep, step);
|
|
const isMatch = isMetaMatchedByFilter(info);
|
|
if (isMatch) rowHasMatch = true;
|
|
|
|
if (sharedStart >= 0 && index === sharedStart) {
|
|
const box = document.createElement('span');
|
|
box.className = 'sm-shared-box';
|
|
const sharedMatches = sharedSegment.map((segmentName) => {
|
|
const segmentInfo = getSecondFlowMeta(mainStep, segmentName);
|
|
const segmentMatch = isMetaMatchedByFilter(segmentInfo);
|
|
if (segmentMatch) rowHasMatch = true;
|
|
return segmentMatch;
|
|
});
|
|
|
|
for (let offset = 0; offset < sharedSegment.length; offset += 1) {
|
|
const segmentName = sharedSegment[offset];
|
|
const segmentInfo = getSecondFlowMeta(mainStep, segmentName);
|
|
const previousStep = offset > 0
|
|
? sharedSegment[offset - 1]
|
|
: (sharedStart > 0 ? sub[sharedStart - 1] : '');
|
|
const previousMatch = offset > 0
|
|
? sharedMatches[offset - 1]
|
|
: (previousStep ? isMetaMatchedByFilter(getSecondFlowMeta(mainStep, previousStep)) : false);
|
|
const dimLink = Boolean(previousStep && hasFilter && !(previousMatch || sharedMatches[offset]));
|
|
box.appendChild(buildStepSegment(mainStep, segmentName, segmentInfo, false, sharedMatches[offset], hasFilter, previousStep, dimLink, flowColIndex));
|
|
flowColIndex += 1;
|
|
}
|
|
|
|
chain.appendChild(box);
|
|
} else {
|
|
const previousStep = index > 0 ? sub[index - 1] : '';
|
|
const previousMatch = previousStep ? isMetaMatchedByFilter(getSecondFlowMeta(mainStep, previousStep)) : false;
|
|
const dimLink = Boolean(previousStep && hasFilter && !(previousMatch || isMatch));
|
|
chain.appendChild(buildStepSegment(mainStep, step, info, false, isMatch, hasFilter, previousStep, dimLink, flowColIndex));
|
|
flowColIndex += 1;
|
|
}
|
|
});
|
|
|
|
if (hasFilter && !rowHasMatch) main.classList.add('dim');
|
|
row.appendChild(chain);
|
|
}
|
|
|
|
fullContentEl.appendChild(row);
|
|
});
|
|
|
|
const commonTitle = document.createElement('p');
|
|
commonTitle.className = 'sm-section-title';
|
|
commonTitle.textContent = '공통';
|
|
fullContentEl.appendChild(commonTitle);
|
|
|
|
if (commonItems.length) {
|
|
commonItems.forEach((item) => {
|
|
const row = document.createElement('div');
|
|
row.className = 'sm-row';
|
|
|
|
const main = document.createElement('div');
|
|
main.className = 'sm-main';
|
|
main.textContent = item;
|
|
if (isActiveFocus('common-root', item, '', true)) main.classList.add('active-focus');
|
|
if (typeof onCommonRootClick === 'function') {
|
|
main.title = `${item} DETAIL 열기`;
|
|
markInteractiveTarget(main, 'common-root', item, '', true);
|
|
}
|
|
row.appendChild(main);
|
|
|
|
const chain = document.createElement('div');
|
|
chain.className = 'sm-chain';
|
|
const sub = commonSubFlow[item] || [];
|
|
let rowHasMatch = false;
|
|
let flowColIndex = 0;
|
|
const isCommonStepMatch = (step) => isMetaMatchedByFilter(getSecondFlowMeta(item, step));
|
|
|
|
const getStepRenderMeta = (label) => {
|
|
const info = getSecondFlowMeta(item, label);
|
|
const isMatch = isCommonStepMatch(label);
|
|
if (isMatch) rowHasMatch = true;
|
|
return { info, isMatch };
|
|
};
|
|
|
|
const sharedStart = findSharedSegmentStart(sub);
|
|
if (sharedStart < 0) {
|
|
sub.forEach((step, index) => {
|
|
const { info, isMatch } = getStepRenderMeta(step);
|
|
const previousStep = index > 0 ? sub[index - 1] : '';
|
|
const previousMatch = previousStep ? isCommonStepMatch(previousStep) : false;
|
|
const dimLink = Boolean(previousStep && hasFilter && !(previousMatch || isMatch));
|
|
chain.appendChild(buildStepSegment(item, step, info, true, isMatch, hasFilter, previousStep, dimLink, flowColIndex));
|
|
flowColIndex += 1;
|
|
});
|
|
} else {
|
|
const prefixSteps = sub.slice(0, sharedStart);
|
|
const sharedSteps = sub.slice(sharedStart, sharedStart + sharedSegment.length);
|
|
const suffixSteps = sub.slice(sharedStart + sharedSegment.length);
|
|
|
|
prefixSteps.forEach((step, index) => {
|
|
const { info, isMatch } = getStepRenderMeta(step);
|
|
const previousStep = index > 0 ? prefixSteps[index - 1] : '';
|
|
const previousMatch = previousStep ? isCommonStepMatch(previousStep) : false;
|
|
const dimLink = Boolean(previousStep && hasFilter && !(previousMatch || isMatch));
|
|
chain.appendChild(buildStepSegment(item, step, info, true, isMatch, hasFilter, previousStep, dimLink, flowColIndex));
|
|
flowColIndex += 1;
|
|
});
|
|
|
|
const box = document.createElement('span');
|
|
box.className = 'sm-shared-box';
|
|
sharedSteps.forEach((step, index) => {
|
|
const { info, isMatch } = getStepRenderMeta(step);
|
|
const previousStep = index > 0
|
|
? sharedSteps[index - 1]
|
|
: (prefixSteps.length ? prefixSteps[prefixSteps.length - 1] : '');
|
|
const previousMatch = previousStep ? isCommonStepMatch(previousStep) : false;
|
|
const dimLink = Boolean(previousStep && hasFilter && !(previousMatch || isMatch));
|
|
box.appendChild(buildStepSegment(item, step, info, true, isMatch, hasFilter, previousStep, dimLink, flowColIndex));
|
|
flowColIndex += 1;
|
|
});
|
|
if (hasFilter && !rowHasMatch) box.classList.add('dim');
|
|
chain.appendChild(box);
|
|
|
|
suffixSteps.forEach((step, index) => {
|
|
const { info, isMatch } = getStepRenderMeta(step);
|
|
const previousStep = index === 0 ? sharedSteps[sharedSteps.length - 1] : suffixSteps[index - 1];
|
|
const previousMatch = previousStep ? isCommonStepMatch(previousStep) : false;
|
|
const dimLink = Boolean(previousStep && hasFilter && !(previousMatch || isMatch));
|
|
chain.appendChild(buildStepSegment(item, step, info, true, isMatch, hasFilter, previousStep, dimLink, flowColIndex));
|
|
flowColIndex += 1;
|
|
});
|
|
}
|
|
|
|
if (hasFilter && !rowHasMatch) main.classList.add('dim');
|
|
row.appendChild(chain);
|
|
fullContentEl.appendChild(row);
|
|
});
|
|
} else {
|
|
const row = document.createElement('div');
|
|
row.className = 'sm-row';
|
|
const main = document.createElement('div');
|
|
main.className = 'sm-main';
|
|
main.textContent = '공통';
|
|
row.appendChild(main);
|
|
const empty = document.createElement('div');
|
|
empty.className = 'empty-state';
|
|
empty.style.minHeight = '120px';
|
|
empty.textContent = '공통 플로우가 없습니다.';
|
|
row.appendChild(empty);
|
|
fullContentEl.appendChild(row);
|
|
}
|
|
|
|
requestAnimationFrame(() => applyColumnAlignment());
|
|
}
|
|
|
|
function setFilters(nextFilters) {
|
|
filters = { ...filters, ...nextFilters };
|
|
}
|
|
|
|
return {
|
|
render,
|
|
refreshFilterOptions,
|
|
setFilters,
|
|
realign: applyColumnAlignment
|
|
};
|
|
}
|
|
|
|
window.createProcessMapRenderer = createProcessMapRenderer;
|
|
})();
|