@dssp/dkpi 1.0.0-alpha.56 → 1.0.0-alpha.58

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22,14 +22,23 @@ export declare class MenuTools extends MenuTools_base {
22
22
  name: string;
23
23
  path: string;
24
24
  icon: string;
25
+ active?: ({ path }: {
26
+ path: string;
27
+ }) => boolean;
25
28
  submenus?: {
26
29
  name: string;
27
30
  path: string;
28
31
  icon?: string;
32
+ active?: ({ path }: {
33
+ path: string;
34
+ }) => boolean;
29
35
  subsubmenus?: {
30
36
  name: string;
31
37
  path: string;
32
38
  icon?: string;
39
+ active?: ({ path }: {
40
+ path: string;
41
+ }) => boolean;
33
42
  }[];
34
43
  }[];
35
44
  }[];
@@ -42,7 +51,7 @@ export declare class MenuTools extends MenuTools_base {
42
51
  private _isActiveMenu;
43
52
  private _isActiveSubmenu;
44
53
  firstUpdated(): Promise<void>;
45
- updated(changedProps: any): void;
54
+ updated(changedProps: Map<string, any>): void;
46
55
  _setSubmenuHeight(): void;
47
56
  connectedCallback(): void;
48
57
  disconnectedCallback(): void;
@@ -57,6 +57,7 @@ let MenuTools = class MenuTools extends connect(store)(LitElement) {
57
57
  name: '홈',
58
58
  path: 'project-list',
59
59
  icon: 'location_city',
60
+ active: ({ path }) => /^project/.test(path),
60
61
  submenus: [
61
62
  { name: '프로젝트 목록', path: 'project-list', icon: 'autoplay' },
62
63
  { name: '완료 프로젝트 목록', path: 'project-completed-list', icon: 'stop_circle' }
@@ -171,7 +172,7 @@ let MenuTools = class MenuTools extends connect(store)(LitElement) {
171
172
  <li>
172
173
  <a
173
174
  href=${sub.subsubmenus ? 'javascript:void(0)' : sub.path}
174
- ?active=${this._isActiveSubmenu(page, sub.path)}
175
+ ?active=${this._isActiveSubmenu(page, sub)}
175
176
  @click=${sub.subsubmenus ? (e) => e.preventDefault() : undefined}
176
177
  >
177
178
  ${sub.icon ? html `<md-icon>${sub.icon}</md-icon>` : ''} ${sub.name}
@@ -208,17 +209,38 @@ let MenuTools = class MenuTools extends connect(store)(LitElement) {
208
209
  `;
209
210
  }
210
211
  _isActiveMenu(currentPage, menu) {
211
- // 메인 메뉴의 active 상태 확인
212
+ // 메인 메뉴의 기본 path 매칭 확인
212
213
  if (currentPage === menu.path)
213
214
  return true;
215
+ // 커스텀 active 함수가 있으면 추가로 확인
216
+ if (menu.active && typeof menu.active === 'function') {
217
+ if (menu.active({ path: currentPage }))
218
+ return true;
219
+ }
214
220
  // 서브메뉴 중 하나라도 active인지 확인
215
221
  if (menu.submenus) {
216
- return menu.submenus.some((sub) => currentPage === sub.path || currentPage.startsWith(sub.path + '/'));
222
+ return menu.submenus.some((sub) => {
223
+ // 서브메뉴 기본 path 매칭
224
+ if (currentPage === sub.path || currentPage.startsWith(sub.path + '/'))
225
+ return true;
226
+ // 서브메뉴에 커스텀 active 함수가 있으면 추가로 확인
227
+ if (sub.active && typeof sub.active === 'function') {
228
+ return sub.active({ path: currentPage });
229
+ }
230
+ return false;
231
+ });
217
232
  }
218
233
  return false;
219
234
  }
220
- _isActiveSubmenu(currentPage, submenuPath) {
221
- return currentPage === submenuPath || currentPage.startsWith(submenuPath + '/');
235
+ _isActiveSubmenu(currentPage, submenu) {
236
+ // 기본 path 매칭 확인
237
+ if (currentPage === submenu.path || currentPage.startsWith(submenu.path + '/'))
238
+ return true;
239
+ // 커스텀 active 함수가 있으면 추가로 확인
240
+ if (submenu.active && typeof submenu.active === 'function') {
241
+ return submenu.active({ path: currentPage });
242
+ }
243
+ return false;
222
244
  }
223
245
  async firstUpdated() {
224
246
  this._setSubmenuHeight();
@@ -252,8 +274,15 @@ let MenuTools = class MenuTools extends connect(store)(LitElement) {
252
274
  this.openedSubmenuIndex = subIndex;
253
275
  }
254
276
  }
255
- _isActiveSubsubmenu(currentPage, subsubmenuPath) {
256
- return currentPage === subsubmenuPath || currentPage.startsWith(subsubmenuPath + '/');
277
+ _isActiveSubsubmenu(currentPage, subsubmenu) {
278
+ // 기본 path 매칭 확인
279
+ if (currentPage === subsubmenu.path || currentPage.startsWith(subsubmenu.path + '/'))
280
+ return true;
281
+ // 커스텀 active 함수가 있으면 추가로 확인
282
+ if (subsubmenu.active && typeof subsubmenu.active === 'function') {
283
+ return subsubmenu.active({ path: currentPage });
284
+ }
285
+ return false;
257
286
  }
258
287
  stateChanged(state) {
259
288
  this.page = state.route.page;
@@ -1 +1 @@
1
- {"version":3,"file":"menu-tools.js","sourceRoot":"","sources":["../../client/viewparts/menu-tools.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\n\nimport { connect } from 'pwa-helpers'\n\nimport { store } from '@operato/shell'\nimport { client } from '@operato/graphql'\nimport { i18next } from '@operato/i18n'\n\n// import { ICONS_HOME, ICONS_KPIS, ICONS_INTEGRATION, ICONS_SETTING } from '../icons/menu-icons'\nexport async function queryDataSets(): Promise<{ id: string; name: string; description: string; active: boolean }[]> {\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {\n responses: dataSets(filters: $filters, pagination: $pagination, sortings: $sortings) {\n items {\n id\n name\n description\n active\n summaryPeriod\n }\n total\n }\n }\n `,\n variables: {\n filters: [\n {\n name: 'active',\n operator: 'eq',\n value: true\n }\n ],\n pagination: {},\n sortings: []\n }\n })\n\n return response.data.responses.items\n}\n\n@customElement('menu-tools')\nexport class MenuTools extends connect(store)(LitElement) {\n static styles = [\n css`\n :host {\n position: relative;\n display: flex;\n background-color: var(--secondary-color);\n\n /* for narrow mode */\n flex-direction: column;\n width: 100%;\n --menu-tools-color: rgba(255, 255, 255, 0.9);\n --menu-tools-active-color: rgba(107, 178, 249, 1);\n }\n\n :host([width='WIDE']) {\n /* for wide mode */\n flex-direction: row;\n width: initial;\n height: 100%;\n }\n\n ul {\n display: flex;\n flex-direction: row;\n\n margin: auto;\n padding: 0;\n list-style: none;\n height: 100%;\n overflow: none;\n }\n\n :host([width='NARROW']) ul {\n width: 100%;\n justify-content: space-around;\n }\n\n :host([width='WIDE']) ul {\n flex-direction: column;\n }\n :host([width='NARROW']) li {\n flex: 1;\n }\n\n :host([width='WIDE']) li {\n border-top: 1px solid rgba(255, 255, 255, 0.1);\n border-bottom: 1px solid rgba(0, 0, 0, 0.1);\n }\n\n a {\n display: flex;\n flex-direction: column;\n padding: 8px 8px 4px 8px;\n opacity: 0.7;\n align-items: center;\n text-align: center;\n text-decoration: none;\n text-transform: capitalize;\n color: var(--menu-tools-color);\n border-left: 2px solid transparent;\n\n --md-icon-size: 36px;\n }\n\n a[active] {\n opacity: 1;\n color: var(--menu-tools-active-color);\n font-weight: bold;\n background-color: rgba(0, 0, 0, 0.15);\n border-left: 2px solid var(--menu-tools-active-color);\n }\n\n :host([width='NARROW']) a {\n padding: 0px 0px 5px 0px;\n opacity: 0.8;\n color: var(--menu-tools-color);\n border-left: none;\n border-top: 2px solid transparent;\n }\n\n :host([width='NARROW']) a[active] {\n opacity: 1;\n color: var(--menu-tools-active-color);\n font-weight: bold;\n background-color: rgba(0, 0, 0, 0.15);\n border-left: none;\n border-top: 2px solid var(--menu-tools-active-color);\n }\n\n img {\n display: block;\n width: 35px;\n padding: 5px 10px 0px 10px;\n }\n\n :host([width='NARROW']) img {\n padding: 0;\n }\n\n div {\n font-size: 0.6em;\n }\n\n .submenu-popup {\n position: absolute;\n left: 60px; /* 탑메뉴 width */\n top: 0;\n width: 220px;\n height: 100%;\n background: linear-gradient(135deg, #1e293b 0%, #334155 100%);\n color: #fff;\n box-shadow: 2px 0 8px rgba(0, 0, 0, 0.15);\n z-index: 1000;\n display: flex;\n flex-direction: column;\n border-radius: 0 10px 10px 0;\n }\n\n .submenu-popup ul {\n list-style: none;\n margin: 0;\n padding: 0;\n }\n\n .submenu-popup li {\n font-size: 1.1em;\n }\n\n .submenu-popup li a {\n color: #fff;\n text-decoration: none;\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 12px;\n transition: background 0.2s;\n padding: 12px 16px;\n }\n\n .submenu-popup li a:hover {\n background: #333;\n }\n\n .submenu-popup li a md-icon {\n --md-icon-size: 20px;\n color: rgba(255, 255, 255, 0.8);\n }\n\n .submenu-popup li a[active] md-icon {\n color: var(--menu-tools-active-color);\n }\n .submenu-popup li {\n position: relative;\n }\n .subsubmenu {\n margin-left: 20px;\n margin-top: 8px;\n border-left: 2px solid rgba(255, 255, 255, 0.2);\n padding-left: 12px;\n }\n .subsubmenu li {\n padding: 8px 16px;\n font-size: 0.9em;\n }\n .subsubmenu li a {\n padding: 8px 12px;\n font-size: 0.85em;\n opacity: 0.8;\n }\n .subsubmenu li a:hover {\n opacity: 1;\n background: rgba(255, 255, 255, 0.1);\n }\n .subsubmenu li a[active] {\n opacity: 1;\n color: var(--menu-tools-active-color);\n background: rgba(107, 178, 249, 0.2);\n }\n .subsubmenu li a md-icon {\n --md-icon-size: 16px;\n }\n .expand-button {\n margin-left: auto;\n background: none;\n border: none;\n color: rgba(255, 255, 255, 0.6);\n cursor: pointer;\n padding: 4px;\n border-radius: 4px;\n transition: all 0.2s;\n }\n .expand-button:hover {\n color: rgba(255, 255, 255, 0.9);\n background: rgba(255, 255, 255, 0.1);\n }\n .expand-button md-icon {\n --md-icon-size: 16px;\n transition: transform 0.2s;\n }\n .expand-button.expanded md-icon {\n transform: rotate(90deg);\n }\n `\n ]\n\n @property({ type: String }) page?: string\n @property({ type: String, reflect: true }) width?: string\n\n @state() dataSets: any[] = []\n @state() menus: {\n name: string\n path: string\n icon: string\n submenus?: { name: string; path: string; icon?: string; subsubmenus?: { name: string; path: string; icon?: string }[] }[]\n }[] = this.getMenus()\n\n private getMenus(): {\n name: string\n path: string\n icon: string\n submenus?: { name: string; path: string; icon?: string; subsubmenus?: { name: string; path: string; icon?: string }[] }[]\n }[] {\n const dataSets = this.dataSets\n\n return [\n {\n name: '홈',\n path: 'project-list',\n icon: 'location_city',\n submenus: [\n { name: '프로젝트 목록', path: 'project-list', icon: 'autoplay' },\n { name: '완료 프로젝트 목록', path: 'project-completed-list', icon: 'stop_circle' }\n ]\n },\n {\n name: 'KPI',\n path: 'kpi-overview',\n icon: 'bar_chart',\n submenus: [\n { name: 'KPI 개요', path: 'kpi-overview', icon: 'widget_small' },\n { name: 'KPI 성과판', path: 'kpi-dashboard', icon: 'dashboard' },\n {\n name: i18next.t('title.kpi statistic list'),\n icon: 'candlestick_chart',\n path: 'kpi-statistic-list'\n },\n {\n name: i18next.t('title.kpi statistic editor'),\n icon: 'edit_square',\n path: 'kpi-statistic-editor'\n },\n { name: 'KPI 목록', path: 'kpi-list', icon: 'readiness_score' },\n { name: 'KPI 값 목록', path: 'kpi-value-list', icon: 'heap_snapshot_multiple' },\n { name: 'KPI 메트릭 목록', path: 'kpi-metric-list', icon: 'data_exploration' },\n { name: 'KPI 메트릭 값 목록', path: 'kpi-metric-value-list', icon: 'heap_snapshot_multiple' },\n { name: 'KPI 매트릭 값 수동 입력', path: 'kpi-metric-value-manual-entry', icon: 'edit' }\n ]\n },\n {\n name: 'Data Set',\n path: 'data-set-list',\n icon: 'dataset',\n submenus: [\n { name: 'Data Set 목록', path: 'data-set-list', icon: 'dataset' },\n { name: 'Data Key 목록', path: 'data-key-set-list', icon: 'key' },\n { name: 'Data Sample 값 수동 입력', path: 'data-entry-list', icon: 'edit' },\n {\n name: 'Data Sample 목록',\n path: 'data-sample-list',\n icon: 'rule',\n subsubmenus:\n dataSets.map(item => {\n return {\n name: item.name,\n icon: 'checklist_rtl',\n path: `data-sample-search/${item.id}`\n }\n }) || []\n },\n {\n name: 'Data Summary 목록',\n path: 'data-summary-list',\n icon: 'functions',\n subsubmenus:\n dataSets\n .filter(item => item.active && item.summaryPeriod)\n .map(item => {\n return {\n name: item.name,\n icon: 'checklist',\n path: `data-summary-period/${item.id}`\n }\n }) || []\n }\n ]\n },\n {\n name: '시스템연계',\n path: 'project-task-list',\n icon: 'hub',\n submenus: [\n { name: '연계 현황', path: 'integration-monitor', icon: 'hub' },\n { name: '연계 설정', path: 'connection', icon: 'settings' },\n { name: '연계 시나리오', path: 'scenario', icon: 'hub' }\n ]\n },\n {\n name: '셋팅',\n path: 'users',\n icon: 'settings',\n submenus: [\n { name: '사용자 관리', path: 'users', icon: 'people' },\n { name: '환경 설정', path: 'setting', icon: 'settings' }\n ]\n }\n ]\n }\n\n @property({ type: Number }) openedMenuIndex: number | null = null\n @property({ type: Number }) openedSubmenuIndex: number | null = null\n @query('ul') menuUl!: HTMLUListElement\n @property({ type: Number }) submenuHeight: number | null = null\n\n render() {\n var page = this.page || ''\n return html`\n <ul>\n ${this.menus.map(\n (menu, i) => html`\n <li\n @click=${(e: Event) => {\n e.stopPropagation()\n this.openedMenuIndex = i\n this._setSubmenuHeight()\n }}\n >\n <a href=\"javascript:void(0)\" ?active=${this._isActiveMenu(page, menu)}>\n <md-icon>${menu.icon}</md-icon>\n <div>${menu.name}</div>\n </a>\n </li>\n `\n )}\n </ul>\n ${this.openedMenuIndex !== null\n ? html`\n <div class=\"submenu-popup\">\n <ul>\n ${this.menus[this.openedMenuIndex].submenus?.map(\n (sub, subIndex) => html`\n <li>\n <a\n href=${sub.subsubmenus ? 'javascript:void(0)' : sub.path}\n ?active=${this._isActiveSubmenu(page, sub.path)}\n @click=${sub.subsubmenus ? (e: Event) => e.preventDefault() : undefined}\n >\n ${sub.icon ? html`<md-icon>${sub.icon}</md-icon>` : ''} ${sub.name}\n ${sub.subsubmenus\n ? html`\n <button\n class=\"expand-button ${this.openedSubmenuIndex === subIndex ? 'expanded' : ''}\"\n @click=${(e: Event) => this._toggleSubsubmenu(e, subIndex)}\n >\n <md-icon>chevron_right</md-icon>\n </button>\n `\n : ''}\n </a>\n ${sub.subsubmenus && this.openedSubmenuIndex === subIndex\n ? html`\n <ul class=\"subsubmenu\">\n ${sub.subsubmenus.map(\n subsub => html`\n <li>\n <a href=${subsub.path} ?active=${this._isActiveSubsubmenu(page, subsub.path)}>\n ${subsub.icon ? html`<md-icon>${subsub.icon}</md-icon>` : ''} ${subsub.name}\n </a>\n </li>\n `\n )}\n </ul>\n `\n : ''}\n </li>\n `\n )}\n </ul>\n </div>\n `\n : ''}\n `\n }\n\n private _isActiveMenu(currentPage: string, menu: any): boolean {\n // 메인 메뉴의 active 상태 확인\n if (currentPage === menu.path) return true\n\n // 서브메뉴 중 하나라도 active인지 확인\n if (menu.submenus) {\n return menu.submenus.some((sub: any) => currentPage === sub.path || currentPage.startsWith(sub.path + '/'))\n }\n\n return false\n }\n\n private _isActiveSubmenu(currentPage: string, submenuPath: string): boolean {\n return currentPage === submenuPath || currentPage.startsWith(submenuPath + '/')\n }\n\n async firstUpdated() {\n this._setSubmenuHeight()\n\n this.dataSets = await queryDataSets()\n this.menus = this.getMenus()\n }\n\n updated(changedProps) {\n if (changedProps.has('openedMenuIndex')) {\n this._setSubmenuHeight()\n }\n }\n\n _setSubmenuHeight() {\n if (this.menuUl) {\n this.submenuHeight = this.menuUl.clientHeight\n }\n }\n\n connectedCallback() {\n super.connectedCallback()\n window.addEventListener('click', this._closeSubmenu)\n }\n\n disconnectedCallback() {\n window.removeEventListener('click', this._closeSubmenu)\n super.disconnectedCallback()\n }\n\n private _closeSubmenu = () => {\n this.openedMenuIndex = null\n }\n\n private _toggleSubsubmenu(e: Event, subIndex: number) {\n e.stopPropagation()\n if (this.openedSubmenuIndex === subIndex) {\n this.openedSubmenuIndex = null\n } else {\n this.openedSubmenuIndex = subIndex\n }\n }\n\n private _isActiveSubsubmenu(currentPage: string, subsubmenuPath: string): boolean {\n return currentPage === subsubmenuPath || currentPage.startsWith(subsubmenuPath + '/')\n }\n\n stateChanged(state) {\n this.page = state.route.page\n this.width = state.layout.width\n }\n}\n"]}
1
+ {"version":3,"file":"menu-tools.js","sourceRoot":"","sources":["../../client/viewparts/menu-tools.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, LitElement } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\n\nimport { connect } from 'pwa-helpers'\n\nimport { store } from '@operato/shell'\nimport { client } from '@operato/graphql'\nimport { i18next } from '@operato/i18n'\n\n// import { ICONS_HOME, ICONS_KPIS, ICONS_INTEGRATION, ICONS_SETTING } from '../icons/menu-icons'\nexport async function queryDataSets(): Promise<{ id: string; name: string; description: string; active: boolean }[]> {\n const response = await client.query({\n query: gql`\n query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {\n responses: dataSets(filters: $filters, pagination: $pagination, sortings: $sortings) {\n items {\n id\n name\n description\n active\n summaryPeriod\n }\n total\n }\n }\n `,\n variables: {\n filters: [\n {\n name: 'active',\n operator: 'eq',\n value: true\n }\n ],\n pagination: {},\n sortings: []\n }\n })\n\n return response.data.responses.items\n}\n\n@customElement('menu-tools')\nexport class MenuTools extends connect(store)(LitElement) {\n static styles = [\n css`\n :host {\n position: relative;\n display: flex;\n background-color: var(--secondary-color);\n\n /* for narrow mode */\n flex-direction: column;\n width: 100%;\n --menu-tools-color: rgba(255, 255, 255, 0.9);\n --menu-tools-active-color: rgba(107, 178, 249, 1);\n }\n\n :host([width='WIDE']) {\n /* for wide mode */\n flex-direction: row;\n width: initial;\n height: 100%;\n }\n\n ul {\n display: flex;\n flex-direction: row;\n\n margin: auto;\n padding: 0;\n list-style: none;\n height: 100%;\n overflow: none;\n }\n\n :host([width='NARROW']) ul {\n width: 100%;\n justify-content: space-around;\n }\n\n :host([width='WIDE']) ul {\n flex-direction: column;\n }\n :host([width='NARROW']) li {\n flex: 1;\n }\n\n :host([width='WIDE']) li {\n border-top: 1px solid rgba(255, 255, 255, 0.1);\n border-bottom: 1px solid rgba(0, 0, 0, 0.1);\n }\n\n a {\n display: flex;\n flex-direction: column;\n padding: 8px 8px 4px 8px;\n opacity: 0.7;\n align-items: center;\n text-align: center;\n text-decoration: none;\n text-transform: capitalize;\n color: var(--menu-tools-color);\n border-left: 2px solid transparent;\n\n --md-icon-size: 36px;\n }\n\n a[active] {\n opacity: 1;\n color: var(--menu-tools-active-color);\n font-weight: bold;\n background-color: rgba(0, 0, 0, 0.15);\n border-left: 2px solid var(--menu-tools-active-color);\n }\n\n :host([width='NARROW']) a {\n padding: 0px 0px 5px 0px;\n opacity: 0.8;\n color: var(--menu-tools-color);\n border-left: none;\n border-top: 2px solid transparent;\n }\n\n :host([width='NARROW']) a[active] {\n opacity: 1;\n color: var(--menu-tools-active-color);\n font-weight: bold;\n background-color: rgba(0, 0, 0, 0.15);\n border-left: none;\n border-top: 2px solid var(--menu-tools-active-color);\n }\n\n img {\n display: block;\n width: 35px;\n padding: 5px 10px 0px 10px;\n }\n\n :host([width='NARROW']) img {\n padding: 0;\n }\n\n div {\n font-size: 0.6em;\n }\n\n .submenu-popup {\n position: absolute;\n left: 60px; /* 탑메뉴 width */\n top: 0;\n width: 220px;\n height: 100%;\n background: linear-gradient(135deg, #1e293b 0%, #334155 100%);\n color: #fff;\n box-shadow: 2px 0 8px rgba(0, 0, 0, 0.15);\n z-index: 1000;\n display: flex;\n flex-direction: column;\n border-radius: 0 10px 10px 0;\n }\n\n .submenu-popup ul {\n list-style: none;\n margin: 0;\n padding: 0;\n }\n\n .submenu-popup li {\n font-size: 1.1em;\n }\n\n .submenu-popup li a {\n color: #fff;\n text-decoration: none;\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 12px;\n transition: background 0.2s;\n padding: 12px 16px;\n }\n\n .submenu-popup li a:hover {\n background: #333;\n }\n\n .submenu-popup li a md-icon {\n --md-icon-size: 20px;\n color: rgba(255, 255, 255, 0.8);\n }\n\n .submenu-popup li a[active] md-icon {\n color: var(--menu-tools-active-color);\n }\n .submenu-popup li {\n position: relative;\n }\n .subsubmenu {\n margin-left: 20px;\n margin-top: 8px;\n border-left: 2px solid rgba(255, 255, 255, 0.2);\n padding-left: 12px;\n }\n .subsubmenu li {\n padding: 8px 16px;\n font-size: 0.9em;\n }\n .subsubmenu li a {\n padding: 8px 12px;\n font-size: 0.85em;\n opacity: 0.8;\n }\n .subsubmenu li a:hover {\n opacity: 1;\n background: rgba(255, 255, 255, 0.1);\n }\n .subsubmenu li a[active] {\n opacity: 1;\n color: var(--menu-tools-active-color);\n background: rgba(107, 178, 249, 0.2);\n }\n .subsubmenu li a md-icon {\n --md-icon-size: 16px;\n }\n .expand-button {\n margin-left: auto;\n background: none;\n border: none;\n color: rgba(255, 255, 255, 0.6);\n cursor: pointer;\n padding: 4px;\n border-radius: 4px;\n transition: all 0.2s;\n }\n .expand-button:hover {\n color: rgba(255, 255, 255, 0.9);\n background: rgba(255, 255, 255, 0.1);\n }\n .expand-button md-icon {\n --md-icon-size: 16px;\n transition: transform 0.2s;\n }\n .expand-button.expanded md-icon {\n transform: rotate(90deg);\n }\n `\n ]\n\n @property({ type: String }) page?: string\n @property({ type: String, reflect: true }) width?: string\n\n @state() dataSets: any[] = []\n @state() menus: {\n name: string\n path: string\n icon: string\n active?: ({ path }: { path: string }) => boolean\n submenus?: {\n name: string\n path: string\n icon?: string\n active?: ({ path }: { path: string }) => boolean\n subsubmenus?: { name: string; path: string; icon?: string; active?: ({ path }: { path: string }) => boolean }[]\n }[]\n }[] = this.getMenus()\n\n private getMenus(): {\n name: string\n path: string\n icon: string\n active?: ({ path }: { path: string }) => boolean\n submenus?: {\n name: string\n path: string\n icon?: string\n active?: ({ path }: { path: string }) => boolean\n subsubmenus?: { name: string; path: string; icon?: string; active?: ({ path }: { path: string }) => boolean }[]\n }[]\n }[] {\n const dataSets = this.dataSets\n\n return [\n {\n name: '홈',\n path: 'project-list',\n icon: 'location_city',\n active: ({ path }) => /^project/.test(path),\n submenus: [\n { name: '프로젝트 목록', path: 'project-list', icon: 'autoplay' },\n { name: '완료 프로젝트 목록', path: 'project-completed-list', icon: 'stop_circle' }\n ]\n },\n {\n name: 'KPI',\n path: 'kpi-overview',\n icon: 'bar_chart',\n submenus: [\n { name: 'KPI 개요', path: 'kpi-overview', icon: 'widget_small' },\n { name: 'KPI 성과판', path: 'kpi-dashboard', icon: 'dashboard' },\n {\n name: i18next.t('title.kpi statistic list'),\n icon: 'candlestick_chart',\n path: 'kpi-statistic-list'\n },\n {\n name: i18next.t('title.kpi statistic editor'),\n icon: 'edit_square',\n path: 'kpi-statistic-editor'\n },\n { name: 'KPI 목록', path: 'kpi-list', icon: 'readiness_score' },\n { name: 'KPI 값 목록', path: 'kpi-value-list', icon: 'heap_snapshot_multiple' },\n { name: 'KPI 메트릭 목록', path: 'kpi-metric-list', icon: 'data_exploration' },\n { name: 'KPI 메트릭 값 목록', path: 'kpi-metric-value-list', icon: 'heap_snapshot_multiple' },\n { name: 'KPI 매트릭 값 수동 입력', path: 'kpi-metric-value-manual-entry', icon: 'edit' }\n ]\n },\n {\n name: 'Data Set',\n path: 'data-set-list',\n icon: 'dataset',\n submenus: [\n { name: 'Data Set 목록', path: 'data-set-list', icon: 'dataset' },\n { name: 'Data Key 목록', path: 'data-key-set-list', icon: 'key' },\n { name: 'Data Sample 값 수동 입력', path: 'data-entry-list', icon: 'edit' },\n {\n name: 'Data Sample 목록',\n path: 'data-sample-list',\n icon: 'rule',\n subsubmenus:\n dataSets.map(item => {\n return {\n name: item.name,\n icon: 'checklist_rtl',\n path: `data-sample-search/${item.id}`\n }\n }) || []\n },\n {\n name: 'Data Summary 목록',\n path: 'data-summary-list',\n icon: 'functions',\n subsubmenus:\n dataSets\n .filter(item => item.active && item.summaryPeriod)\n .map(item => {\n return {\n name: item.name,\n icon: 'checklist',\n path: `data-summary-period/${item.id}`\n }\n }) || []\n }\n ]\n },\n {\n name: '시스템연계',\n path: 'project-task-list',\n icon: 'hub',\n submenus: [\n { name: '연계 현황', path: 'integration-monitor', icon: 'hub' },\n { name: '연계 설정', path: 'connection', icon: 'settings' },\n { name: '연계 시나리오', path: 'scenario', icon: 'hub' }\n ]\n },\n {\n name: '셋팅',\n path: 'users',\n icon: 'settings',\n submenus: [\n { name: '사용자 관리', path: 'users', icon: 'people' },\n { name: '환경 설정', path: 'setting', icon: 'settings' }\n ]\n }\n ]\n }\n\n @property({ type: Number }) openedMenuIndex: number | null = null\n @property({ type: Number }) openedSubmenuIndex: number | null = null\n @query('ul') menuUl!: HTMLUListElement\n @property({ type: Number }) submenuHeight: number | null = null\n\n render() {\n var page = this.page || ''\n return html`\n <ul>\n ${this.menus.map(\n (menu, i) => html`\n <li\n @click=${(e: Event) => {\n e.stopPropagation()\n this.openedMenuIndex = i\n this._setSubmenuHeight()\n }}\n >\n <a href=\"javascript:void(0)\" ?active=${this._isActiveMenu(page, menu)}>\n <md-icon>${menu.icon}</md-icon>\n <div>${menu.name}</div>\n </a>\n </li>\n `\n )}\n </ul>\n ${this.openedMenuIndex !== null\n ? html`\n <div class=\"submenu-popup\">\n <ul>\n ${this.menus[this.openedMenuIndex].submenus?.map(\n (sub, subIndex) => html`\n <li>\n <a\n href=${sub.subsubmenus ? 'javascript:void(0)' : sub.path}\n ?active=${this._isActiveSubmenu(page, sub)}\n @click=${sub.subsubmenus ? (e: Event) => e.preventDefault() : undefined}\n >\n ${sub.icon ? html`<md-icon>${sub.icon}</md-icon>` : ''} ${sub.name}\n ${sub.subsubmenus\n ? html`\n <button\n class=\"expand-button ${this.openedSubmenuIndex === subIndex ? 'expanded' : ''}\"\n @click=${(e: Event) => this._toggleSubsubmenu(e, subIndex)}\n >\n <md-icon>chevron_right</md-icon>\n </button>\n `\n : ''}\n </a>\n ${sub.subsubmenus && this.openedSubmenuIndex === subIndex\n ? html`\n <ul class=\"subsubmenu\">\n ${sub.subsubmenus.map(\n subsub => html`\n <li>\n <a href=${subsub.path} ?active=${this._isActiveSubsubmenu(page, subsub.path)}>\n ${subsub.icon ? html`<md-icon>${subsub.icon}</md-icon>` : ''} ${subsub.name}\n </a>\n </li>\n `\n )}\n </ul>\n `\n : ''}\n </li>\n `\n )}\n </ul>\n </div>\n `\n : ''}\n `\n }\n\n private _isActiveMenu(currentPage: string, menu: any): boolean {\n // 메인 메뉴의 기본 path 매칭 확인\n if (currentPage === menu.path) return true\n\n // 커스텀 active 함수가 있으면 추가로 확인\n if (menu.active && typeof menu.active === 'function') {\n if (menu.active({ path: currentPage })) return true\n }\n\n // 서브메뉴 중 하나라도 active인지 확인\n if (menu.submenus) {\n return menu.submenus.some((sub: any) => {\n // 서브메뉴 기본 path 매칭\n if (currentPage === sub.path || currentPage.startsWith(sub.path + '/')) return true\n\n // 서브메뉴에 커스텀 active 함수가 있으면 추가로 확인\n if (sub.active && typeof sub.active === 'function') {\n return sub.active({ path: currentPage })\n }\n\n return false\n })\n }\n\n return false\n }\n\n private _isActiveSubmenu(currentPage: string, submenu: any): boolean {\n // 기본 path 매칭 확인\n if (currentPage === submenu.path || currentPage.startsWith(submenu.path + '/')) return true\n\n // 커스텀 active 함수가 있으면 추가로 확인\n if (submenu.active && typeof submenu.active === 'function') {\n return submenu.active({ path: currentPage })\n }\n\n return false\n }\n\n async firstUpdated() {\n this._setSubmenuHeight()\n\n this.dataSets = await queryDataSets()\n this.menus = this.getMenus()\n }\n\n updated(changedProps: Map<string, any>) {\n if (changedProps.has('openedMenuIndex')) {\n this._setSubmenuHeight()\n }\n }\n\n _setSubmenuHeight() {\n if (this.menuUl) {\n this.submenuHeight = this.menuUl.clientHeight\n }\n }\n\n connectedCallback() {\n super.connectedCallback()\n window.addEventListener('click', this._closeSubmenu)\n }\n\n disconnectedCallback() {\n window.removeEventListener('click', this._closeSubmenu)\n super.disconnectedCallback()\n }\n\n private _closeSubmenu = () => {\n this.openedMenuIndex = null\n }\n\n private _toggleSubsubmenu(e: Event, subIndex: number) {\n e.stopPropagation()\n if (this.openedSubmenuIndex === subIndex) {\n this.openedSubmenuIndex = null\n } else {\n this.openedSubmenuIndex = subIndex\n }\n }\n\n private _isActiveSubsubmenu(currentPage: string, subsubmenu: any): boolean {\n // 기본 path 매칭 확인\n if (currentPage === subsubmenu.path || currentPage.startsWith(subsubmenu.path + '/')) return true\n\n // 커스텀 active 함수가 있으면 추가로 확인\n if (subsubmenu.active && typeof subsubmenu.active === 'function') {\n return subsubmenu.active({ path: currentPage })\n }\n\n return false\n }\n\n stateChanged(state: any) {\n this.page = state.route.page\n this.width = state.layout.width\n }\n}\n"]}
@@ -5,4 +5,42 @@ export declare class KpiMetricValueMutation {
5
5
  updateKpiMetricValuesAssessment(patches: KpiMetricValuePatch[], context: ResolverContext): Promise<KpiMetricValue[]>;
6
6
  updateKpiMetricValuesSentiment(attachment: NewAttachment, context: ResolverContext): Promise<Attachment | null>;
7
7
  finalizeProjectWithKpiRecalculation(projectId: string, context: ResolverContext): Promise<boolean>;
8
+ /**
9
+ * 프로젝트의 모든 KPI Value들을 계층적으로 재계산합니다.
10
+ * 1. Leaf KPIs (자식이 없는 KPI): formula 기반 계산
11
+ * 2. Parent KPIs (자식이 있는 KPI): 자식 KPI scores의 weighted average 계산
12
+ */
13
+ private recalculateProjectKpiValues;
14
+ /**
15
+ * 개별 KPI Value를 재계산합니다.
16
+ */
17
+ private recalculateKpiValue;
18
+ /**
19
+ * scoreFormula를 사용한 성과 점수 계산
20
+ */
21
+ private calculateScoreFromFormula;
22
+ /**
23
+ * KPI의 grades lookup table을 이용한 성과 점수 변환
24
+ */
25
+ private calculateScoreFromLookup;
26
+ /**
27
+ * KpiValue의 성과 점수 계산 및 저장
28
+ */
29
+ private calculateAndSaveScore;
30
+ /**
31
+ * Leaf KPI Value를 formula로 계산합니다.
32
+ */
33
+ private recalculateLeafKpiValue;
34
+ /**
35
+ * Parent KPI Value를 자식 KPI scores의 weighted average로 계산합니다.
36
+ */
37
+ private recalculateParentKpiValue;
38
+ /**
39
+ * KPIs를 계층 레벨순으로 정렬합니다 (깊은 레벨부터)
40
+ */
41
+ private sortKpisByLevel;
42
+ /**
43
+ * 자식 KPI values의 weighted average를 계산합니다.
44
+ */
45
+ private calculateWeightedAverage;
8
46
  }
@@ -4,12 +4,11 @@ exports.KpiMetricValueMutation = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const type_graphql_1 = require("type-graphql");
6
6
  const shell_1 = require("@things-factory/shell");
7
+ const typeorm_1 = require("typeorm");
7
8
  const kpi_1 = require("@things-factory/kpi");
8
9
  const attachment_base_1 = require("@things-factory/attachment-base");
9
10
  const project_1 = require("@dssp/project");
10
11
  const moment_timezone_1 = tslib_1.__importDefault(require("moment-timezone"));
11
- // import { ScalarObject } from '@things-factory/shell'
12
- // import { getDefaultValueDate } from '@things-factory/kpi/utils/value-date-util'
13
12
  let KpiMetricValueMutation = class KpiMetricValueMutation {
14
13
  /* 프로젝트 수행과정 중에 (시간, 비용, 발생 건수 등) 누적된 값을 반영하여 KPI Metric Value 값을 수정 - 예를 들면, 총 설계 변경 건수, 총 건설 폐기물 발생량, 공사비, 공사기간, 재해 건수 등 */
15
14
  async updateKpiMetricValuesCumulative(patches, context) {
@@ -94,9 +93,319 @@ let KpiMetricValueMutation = class KpiMetricValueMutation {
94
93
  if (!project) {
95
94
  throw new Error(`Project not found: ${projectId}`);
96
95
  }
96
+ // 이지점에서 프로젝트 KPI Value 계산을 다시 할 것이다.
97
+ await this.recalculateProjectKpiValues(projectId, context);
97
98
  projectRepo.save(Object.assign(Object.assign({}, project), { state: project_1.ProjectState.COMPLETED }));
98
99
  return true;
99
100
  }
101
+ /**
102
+ * 프로젝트의 모든 KPI Value들을 계층적으로 재계산합니다.
103
+ * 1. Leaf KPIs (자식이 없는 KPI): formula 기반 계산
104
+ * 2. Parent KPIs (자식이 있는 KPI): 자식 KPI scores의 weighted average 계산
105
+ */
106
+ async recalculateProjectKpiValues(projectId, context) {
107
+ const { domain, user, tx } = context.state;
108
+ const kpiValueRepo = (0, shell_1.getRepository)(kpi_1.KpiValue, tx);
109
+ const kpiRepo = (0, shell_1.getRepository)(kpi_1.Kpi, tx);
110
+ try {
111
+ // 1. 모든 KPI들을 조회 (계층 구조 포함)
112
+ const allKpis = await kpiRepo.find({
113
+ where: { domain: { id: domain.id } },
114
+ relations: ['parent', 'children']
115
+ });
116
+ console.log(`Found ${allKpis.length} KPIs total for domain`);
117
+ // 2. Leaf KPIs (자식이 없는 KPI)와 Parent KPIs 구분
118
+ const leafKpis = allKpis.filter(kpi => kpi.isLeaf || !kpi.children || kpi.children.length === 0);
119
+ const parentKpis = allKpis.filter(kpi => !kpi.isLeaf && kpi.children && kpi.children.length > 0);
120
+ console.log(`Processing ${leafKpis.length} leaf KPIs and ${parentKpis.length} parent KPIs`);
121
+ // 3. 먼저 Leaf KPIs를 formula로 계산
122
+ for (const leafKpi of leafKpis) {
123
+ try {
124
+ await this.recalculateLeafKpiValue(leafKpi, projectId, context);
125
+ console.log(`Successfully calculated leaf KPI: ${leafKpi.name}`);
126
+ }
127
+ catch (error) {
128
+ console.error(`Failed to calculate leaf KPI ${leafKpi.name}:`, error.message);
129
+ // 개별 KPI 계산 실패는 전체 프로세스를 중단하지 않음
130
+ }
131
+ }
132
+ // 4. Parent KPIs를 계층 레벨순으로 정렬 (깊은 레벨부터)
133
+ const sortedParentKpis = this.sortKpisByLevel(parentKpis, allKpis);
134
+ // 5. Parent KPIs를 weighted average로 계산
135
+ for (const parentKpi of sortedParentKpis) {
136
+ try {
137
+ await this.recalculateParentKpiValue(parentKpi, projectId, allKpis, context);
138
+ console.log(`Successfully calculated parent KPI: ${parentKpi.name}`);
139
+ }
140
+ catch (error) {
141
+ console.error(`Failed to calculate parent KPI ${parentKpi.name}:`, error.message);
142
+ }
143
+ }
144
+ }
145
+ catch (error) {
146
+ console.error(`Error recalculating KPI values for project ${projectId}:`, error);
147
+ throw error;
148
+ }
149
+ }
150
+ /**
151
+ * 개별 KPI Value를 재계산합니다.
152
+ */
153
+ async recalculateKpiValue(kpiValue, context) {
154
+ const { domain, user, tx } = context.state;
155
+ const kpiValueRepo = (0, shell_1.getRepository)(kpi_1.KpiValue, tx);
156
+ const kpiRepo = (0, shell_1.getRepository)(kpi_1.Kpi, tx);
157
+ const metricRepo = (0, shell_1.getRepository)(kpi_1.KpiMetric, tx);
158
+ const metricValueRepo = (0, shell_1.getRepository)(kpi_1.KpiMetricValue, tx);
159
+ const kpi = kpiValue.kpi || (await kpiRepo.findOne({ where: { id: kpiValue.kpiId, domain: { id: domain.id } } }));
160
+ if (!kpi)
161
+ throw new Error('KPI 정보 없음');
162
+ if (!kpi.formula) {
163
+ console.log(`KPI ${kpi.name} has no formula, skipping recalculation`);
164
+ return kpiValue;
165
+ }
166
+ const periodType = kpi.periodType || kpi_1.KpiPeriodType.DAY;
167
+ const valueDate = null; // kpiValue.valueDate
168
+ const org = kpiValue.group || 'unknown'; // project ID를 org로 사용
169
+ // things-factory calculator 기반 formula 계산
170
+ let kpiValueResult;
171
+ try {
172
+ const ast = (0, kpi_1.parseFormula)(kpi.formula);
173
+ const provider = new kpi_1.KpiMetricValueProvider({
174
+ valueDate,
175
+ org,
176
+ domainId: domain.id,
177
+ tx
178
+ });
179
+ const evalContext = { functions: kpi_1.builtinFunctions, provider };
180
+ kpiValueResult = await (0, kpi_1.evaluateFormula)(ast, evalContext);
181
+ if (kpiValueResult == null || isNaN(kpiValueResult)) {
182
+ throw new Error('KPI formula 결과값 없음');
183
+ }
184
+ }
185
+ catch (error) {
186
+ console.error(`Formula evaluation failed for KPI ${kpi.name}: ${kpi.formula}`, error);
187
+ return kpiValue; // 계산 실패시 기존 값 유지
188
+ }
189
+ // KPI Value 업데이트
190
+ kpiValue.value = kpiValueResult;
191
+ kpiValue.updater = user;
192
+ // 성과 점수 자동 계산 및 저장
193
+ try {
194
+ await this.calculateAndSaveScore(kpiValue, kpi);
195
+ console.log(`Successfully calculated score for KPI value ${kpiValue.id}`);
196
+ }
197
+ catch (scoreError) {
198
+ console.error(`Failed to calculate score for KPI value ${kpiValue.id}:`, scoreError.message);
199
+ // 성과 점수 계산 실패는 KPI Value 저장을 막지 않음
200
+ }
201
+ return await kpiValueRepo.save(kpiValue);
202
+ }
203
+ /**
204
+ * scoreFormula를 사용한 성과 점수 계산
205
+ */
206
+ async calculateScoreFromFormula(kpi, value) {
207
+ if (!kpi.scoreFormula) {
208
+ return null;
209
+ }
210
+ try {
211
+ const ast = (0, kpi_1.parseFormula)(kpi.scoreFormula);
212
+ const provider = {
213
+ get: async (name) => {
214
+ if (name === 'value')
215
+ return value;
216
+ return null;
217
+ }
218
+ };
219
+ const evalContext = { functions: kpi_1.builtinFunctions, provider };
220
+ const result = await (0, kpi_1.evaluateFormula)(ast, evalContext);
221
+ if (result !== null && result !== undefined && !isNaN(result)) {
222
+ const performanceScore = Number(result);
223
+ // score를 0~1 범위로 제한
224
+ const clampedScore = Math.max(0, Math.min(1, performanceScore));
225
+ return {
226
+ score: clampedScore,
227
+ calculationMethod: 'formula'
228
+ };
229
+ }
230
+ }
231
+ catch (error) {
232
+ console.error(`Score formula evaluation for '${kpi.name}: ${kpi.scoreFormula}' error:`, error);
233
+ }
234
+ return null;
235
+ }
236
+ /**
237
+ * KPI의 grades lookup table을 이용한 성과 점수 변환
238
+ */
239
+ calculateScoreFromLookup(kpi, value) {
240
+ if (!kpi.grades || kpi.grades.length === 0) {
241
+ return null;
242
+ }
243
+ // grades lookup table에서 해당 값의 범위에 맞는 성과 점수 찾기
244
+ const grade = kpi.grades.find((g) => value >= g.minValue && value <= g.maxValue);
245
+ if (!grade) {
246
+ return null;
247
+ }
248
+ // score를 0~1 범위로 제한
249
+ const clampedScore = Math.max(0, Math.min(1, grade.score));
250
+ return {
251
+ score: clampedScore,
252
+ calculationMethod: 'lookup'
253
+ };
254
+ }
255
+ /**
256
+ * KpiValue의 성과 점수 계산 및 저장
257
+ */
258
+ async calculateAndSaveScore(kpiValue, kpi) {
259
+ // 1. scoreFormula 우선 시도
260
+ let scoreResult = await this.calculateScoreFromFormula(kpi, kpiValue.value);
261
+ // 2. scoreFormula가 없거나 실패하면 grades lookup table 사용
262
+ if (!scoreResult) {
263
+ scoreResult = this.calculateScoreFromLookup(kpi, kpiValue.value);
264
+ }
265
+ if (scoreResult) {
266
+ // KpiValue의 score 필드에 성과 점수 저장
267
+ kpiValue.score = scoreResult.score;
268
+ console.log(`Calculated score ${scoreResult.score} for KPI ${kpi.name} with method ${scoreResult.calculationMethod}`);
269
+ }
270
+ else {
271
+ console.log(`No score calculation method available for KPI ${kpi.name}`);
272
+ }
273
+ }
274
+ /**
275
+ * Leaf KPI Value를 formula로 계산합니다.
276
+ */
277
+ async recalculateLeafKpiValue(kpi, projectId, context) {
278
+ const { domain, user, tx } = context.state;
279
+ const kpiValueRepo = (0, shell_1.getRepository)(kpi_1.KpiValue, tx);
280
+ // 기존 KPI Value 조회 또는 생성
281
+ let kpiValue = await kpiValueRepo.findOne({
282
+ where: {
283
+ kpi: { id: kpi.id },
284
+ group: projectId,
285
+ domain: { id: domain.id }
286
+ }
287
+ });
288
+ if (!kpiValue) {
289
+ // 새 KPI Value 생성
290
+ kpiValue = kpiValueRepo.create({
291
+ kpi,
292
+ group: projectId,
293
+ domain,
294
+ valueDate: new Date().toISOString().slice(0, 10),
295
+ value: 0,
296
+ score: 0,
297
+ updater: user
298
+ });
299
+ }
300
+ // formula 기반 계산
301
+ await this.recalculateKpiValue(kpiValue, context);
302
+ }
303
+ /**
304
+ * Parent KPI Value를 자식 KPI scores의 weighted average로 계산합니다.
305
+ */
306
+ async recalculateParentKpiValue(parentKpi, projectId, allKpis, context) {
307
+ const { domain, user, tx } = context.state;
308
+ const kpiValueRepo = (0, shell_1.getRepository)(kpi_1.KpiValue, tx);
309
+ // 자식 KPIs 조회
310
+ const childKpis = allKpis.filter(kpi => { var _a; return ((_a = kpi.parent) === null || _a === void 0 ? void 0 : _a.id) === parentKpi.id; });
311
+ if (childKpis.length === 0) {
312
+ console.log(`No child KPIs found for parent KPI: ${parentKpi.name}`);
313
+ return;
314
+ }
315
+ // 자식 KPI Values 조회
316
+ const childKpiIds = childKpis.map(kpi => kpi.id);
317
+ const childKpiValues = await kpiValueRepo.find({
318
+ where: {
319
+ kpi: { id: (0, typeorm_1.In)(childKpiIds) },
320
+ group: projectId,
321
+ domain: { id: domain.id }
322
+ },
323
+ relations: ['kpi']
324
+ });
325
+ if (childKpiValues.length === 0) {
326
+ console.log(`No child KPI values found for parent KPI: ${parentKpi.name}`);
327
+ return;
328
+ }
329
+ // Weighted average 계산
330
+ const { weightedValue, weightedScore } = this.calculateWeightedAverage(childKpiValues, childKpis);
331
+ // 기존 Parent KPI Value 조회 또는 생성
332
+ let parentKpiValue = await kpiValueRepo.findOne({
333
+ where: {
334
+ kpi: { id: parentKpi.id },
335
+ group: projectId,
336
+ domain: { id: domain.id }
337
+ }
338
+ });
339
+ if (parentKpiValue) {
340
+ // 기존 값 업데이트
341
+ parentKpiValue.value = weightedValue;
342
+ parentKpiValue.score = weightedScore;
343
+ parentKpiValue.updater = user;
344
+ }
345
+ else {
346
+ // 새 값 생성
347
+ parentKpiValue = kpiValueRepo.create({
348
+ kpi: parentKpi,
349
+ group: projectId,
350
+ domain,
351
+ valueDate: new Date().toISOString().slice(0, 10),
352
+ value: weightedValue,
353
+ score: weightedScore,
354
+ updater: user
355
+ });
356
+ }
357
+ await kpiValueRepo.save(parentKpiValue);
358
+ console.log(`Updated parent KPI ${parentKpi.name}: value=${weightedValue.toFixed(4)}, score=${weightedScore.toFixed(4)}`);
359
+ }
360
+ /**
361
+ * KPIs를 계층 레벨순으로 정렬합니다 (깊은 레벨부터)
362
+ */
363
+ sortKpisByLevel(parentKpis, allKpis) {
364
+ const kpiLevels = new Map();
365
+ const calculateLevel = (kpi) => {
366
+ if (kpiLevels.has(kpi.id)) {
367
+ return kpiLevels.get(kpi.id);
368
+ }
369
+ if (!kpi.parent) {
370
+ kpiLevels.set(kpi.id, 0);
371
+ return 0;
372
+ }
373
+ const parentKpi = allKpis.find(k => { var _a; return k.id === ((_a = kpi.parent) === null || _a === void 0 ? void 0 : _a.id); });
374
+ if (!parentKpi) {
375
+ kpiLevels.set(kpi.id, 1);
376
+ return 1;
377
+ }
378
+ const level = calculateLevel(parentKpi) + 1;
379
+ kpiLevels.set(kpi.id, level);
380
+ return level;
381
+ };
382
+ // 각 parent KPI의 레벨 계산
383
+ parentKpis.forEach(kpi => calculateLevel(kpi));
384
+ // 레벨 순으로 정렬 (깊은 레벨부터)
385
+ return parentKpis.sort((a, b) => (kpiLevels.get(b.id) || 0) - (kpiLevels.get(a.id) || 0));
386
+ }
387
+ /**
388
+ * 자식 KPI values의 weighted average를 계산합니다.
389
+ */
390
+ calculateWeightedAverage(childValues, childKpis) {
391
+ let totalWeightedValue = 0;
392
+ let totalWeightedScore = 0;
393
+ let totalWeight = 0;
394
+ for (const value of childValues) {
395
+ const childKpi = childKpis.find(kpi => kpi.id === value.kpi.id);
396
+ const weight = (childKpi === null || childKpi === void 0 ? void 0 : childKpi.weight) || 1;
397
+ totalWeightedValue += value.score * weight;
398
+ totalWeightedScore += value.score * weight;
399
+ totalWeight += weight;
400
+ }
401
+ if (totalWeight === 0) {
402
+ return { weightedValue: 0, weightedScore: 0 };
403
+ }
404
+ return {
405
+ weightedValue: totalWeightedValue / totalWeight,
406
+ weightedScore: totalWeightedScore / totalWeight
407
+ };
408
+ }
100
409
  };
101
410
  exports.KpiMetricValueMutation = KpiMetricValueMutation;
102
411
  tslib_1.__decorate([