@dssp/dkpi 1.0.0-alpha.86 → 1.0.0-alpha.88

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.
Files changed (65) hide show
  1. package/_index.html +4 -4
  2. package/assets/helps/index.md +1 -1
  3. package/assets/manifest.json +2 -2
  4. package/dist-client/components/kpi-boxplot-chart.d.ts +7 -0
  5. package/dist-client/components/kpi-boxplot-chart.js +20 -5
  6. package/dist-client/components/kpi-boxplot-chart.js.map +1 -1
  7. package/dist-client/components/kpi-lookup-chart.js +1 -17
  8. package/dist-client/components/kpi-lookup-chart.js.map +1 -1
  9. package/dist-client/components/kpi-radar-chart.js +2 -2
  10. package/dist-client/components/kpi-radar-chart.js.map +1 -1
  11. package/dist-client/components/kpi-single-boxplot-chart.d.ts +7 -0
  12. package/dist-client/components/kpi-single-boxplot-chart.js +27 -26
  13. package/dist-client/components/kpi-single-boxplot-chart.js.map +1 -1
  14. package/dist-client/components/kpi-step-lookup-chart.d.ts +33 -0
  15. package/dist-client/components/kpi-step-lookup-chart.js +181 -0
  16. package/dist-client/components/kpi-step-lookup-chart.js.map +1 -0
  17. package/dist-client/components/kpi-trend-chart.js +2 -12
  18. package/dist-client/components/kpi-trend-chart.js.map +1 -1
  19. package/dist-client/google-map/common-google-map.js +5 -1
  20. package/dist-client/google-map/common-google-map.js.map +1 -1
  21. package/dist-client/pages/kpi-admin/dssp-kpi-overview.d.ts +2 -1
  22. package/dist-client/pages/kpi-admin/dssp-kpi-overview.js +24 -9
  23. package/dist-client/pages/kpi-admin/dssp-kpi-overview.js.map +1 -1
  24. package/dist-client/pages/kpi-admin/kpi-system-guide.js +311 -158
  25. package/dist-client/pages/kpi-admin/kpi-system-guide.js.map +1 -1
  26. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.d.ts +6 -0
  27. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js +110 -50
  28. package/dist-client/pages/kpi-dashboard/components/kpi-left-panel.js.map +1 -1
  29. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.d.ts +0 -9
  30. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js +6 -91
  31. package/dist-client/pages/kpi-dashboard/components/kpi-map-panel.js.map +1 -1
  32. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.d.ts +2 -0
  33. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js +148 -68
  34. package/dist-client/pages/kpi-dashboard/components/kpi-region-popup.js.map +1 -1
  35. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js +85 -41
  36. package/dist-client/pages/kpi-dashboard/kpi-dashboard-map.js.map +1 -1
  37. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js +15 -7
  38. package/dist-client/pages/kpi-dashboard/kpi-dashboard.js.map +1 -1
  39. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js +16 -25
  40. package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js.map +1 -1
  41. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js +9 -13
  42. package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js.map +1 -1
  43. package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.js +9 -15
  44. package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.js.map +1 -1
  45. package/dist-client/pages/sv-project-complete.js +7 -11
  46. package/dist-client/pages/sv-project-complete.js.map +1 -1
  47. package/dist-client/pages/sv-project-completed-list.js +4 -4
  48. package/dist-client/pages/sv-project-completed-list.js.map +1 -1
  49. package/dist-client/pages/sv-project-detail.d.ts +1 -0
  50. package/dist-client/pages/sv-project-detail.js +94 -82
  51. package/dist-client/pages/sv-project-detail.js.map +1 -1
  52. package/dist-client/pages/sv-project-list.js +15 -12
  53. package/dist-client/pages/sv-project-list.js.map +1 -1
  54. package/dist-client/tsconfig.tsbuildinfo +1 -1
  55. package/dist-client/viewparts/menu-tools.js +17 -3
  56. package/dist-client/viewparts/menu-tools.js.map +1 -1
  57. package/dist-server/service/kpi-stat/kpi-stat-query.js +20 -19
  58. package/dist-server/service/kpi-stat/kpi-stat-query.js.map +1 -1
  59. package/dist-server/tsconfig.tsbuildinfo +1 -1
  60. package/openapi/unstable.yaml +2 -2
  61. package/package.json +3 -3
  62. package/schema.graphql +20 -0
  63. package/translations/ko.json +2 -2
  64. package/views/auth-page.html +4 -4
  65. package/views/public/home.html +5 -5
@@ -1 +1 @@
1
- {"version":3,"file":"kpi-system-guide.js","sourceRoot":"","sources":["../../../client/pages/kpi-admin/kpi-system-guide.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,KAAK,CAAA;AACxC,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAA;AACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,GAAG,MAAM,aAAa,CAAA;AAE7B;;;;;GAKG;AAEH,MAAM,cAAc,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkCzB,CAAA;AAED,MAAM,iBAAiB,GAAqD;IAC1E,MAAM,EAAE,EAAE,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,EAAE;IAClD,OAAO,EAAE,EAAE,KAAK,EAAE,iBAAiB,EAAE,KAAK,EAAE,SAAS,EAAE;IACvD,MAAM,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,SAAS,EAAE;IACnD,MAAM,EAAE,EAAE,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,SAAS,EAAE;CACtD,CAAA;AAED,oBAAoB;AACpB,MAAM,oBAAoB,GAA2B;IACnD,WAAW,EAAE,0BAA0B;IACvC,aAAa,EAAE,wBAAwB;IACvC,cAAc,EAAE,2BAA2B;IAC3C,OAAO,EAAE,sBAAsB;CAChC,CAAA;AAED,MAAM,iBAAiB,GAAmE;IACxF,QAAQ,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE;IAC1D,QAAQ,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE;IAC3D,UAAU,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE;IAC5D,SAAS,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE;CAC5D,CAAA;AAGM,IAAM,cAAc,GAApB,MAAM,cAAe,SAAQ,QAAQ;IAArC;;QA8LI,YAAO,GAAU,EAAE,CAAA;QACnB,YAAO,GAAU,EAAE,CAAA;QACnB,YAAO,GAAG,IAAI,CAAA;QACd,mBAAc,GAAQ,IAAI,CAAA;IA2QrC,CAAC;IAzQC,IAAI,OAAO;QACT,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,CAAA;IACjC,CAAC;IAED,KAAK,CAAC,YAAY;;QAChB,IAAI,CAAC;YACH,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAA;YAC9D,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,EAAE,CAAA;YACjC,IAAI,CAAC,OAAO,GAAG,CAAA,MAAA,IAAI,CAAC,UAAU,0CAAE,KAAK,KAAI,EAAE,CAAA;QAC7C,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,iCAAiC,EAAE,CAAC,CAAC,CAAA;QACrD,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,OAAO,GAAG,KAAK,CAAA;QACtB,CAAC;IACH,CAAC;IAED,yCAAyC;IACjC,cAAc,CAAC,UAAkB;;QACvC,MAAM,IAAI,GAAa,EAAE,CAAA;QACzB,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAC7B,IAAI,CAAC,CAAC,MAAM,KAAK,KAAK;gBAAE,SAAQ;YAChC,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE,CAAC;gBAC/B,IAAI,CAAC,CAAC,MAAM,KAAK,KAAK;oBAAE,SAAQ;gBAChC,IAAI,MAAA,CAAC,CAAC,OAAO,0CAAE,QAAQ,CAAC,IAAI,UAAU,GAAG,CAAC,EAAE,CAAC;oBAC3C,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;gBACnB,CAAC;YACH,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IAEO,UAAU,CAAC,IAAS,EAAE,IAAsB;QAClD,IAAI,CAAC,cAAc,mCAAQ,IAAI,KAAE,KAAK,EAAE,IAAI,GAAE,CAAA;IAChD,CAAC;IAEO,WAAW;QACjB,IAAI,CAAC,cAAc,GAAG,IAAI,CAAA;IAC5B,CAAC;IAED,MAAM;QACJ,IAAI,IAAI,CAAC,OAAO;YAAE,OAAO,IAAI,CAAA,uEAAuE,CAAA;QAEpG,gBAAgB;QAChB,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC,CAAA;QAEpG,OAAO,IAAI,CAAA;;;;;;;;;;UAUL,MAAM,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAChD,IAAI,CAAA,8DAA8D,CAAC,CAAC,KAAK,gBAAgB,CAC1F;;;UAGC,MAAM,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAChD,IAAI,CAAA,8BAA8B,CAAC,CAAC,IAAI,oCAAoC,CAAC,CAAC,KAAK,gBAAgB,CACpG;;;;;;;;qCAQ4B,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,6BAA6B,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,EAAE,EAAE,KAAK,CAAC;;qCAElK,IAAI,CAAC,OAAO;aACpC,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,KAAK,CAAC;aACtC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;aACtD,IAAI,CAAC,KAAK,CAAC;;;;UAId,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE;YACnE,MAAM,UAAU,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,KAAK,CAAC,CAAA;YACxE,OAAO,IAAI,CAAA;;6CAEwB,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,EAAE,KAAK,CAAC;uCACrC,CAAC,CAAC,IAAI;8CACC,CAAC,CAAC,MAAM;;;kBAGpC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAA,0BAA0B,CAAC,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC,EAAE;;oBAE9D,UAAU,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE;gBAC1B,MAAM,EAAE,GAAG,iBAAiB,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,SAAS,IAAI,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;gBAC3F,MAAM,EAAE,GAAG,iBAAiB,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,SAAS,IAAI,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,CAAA;gBACtG,OAAO,IAAI,CAAA;mDACoB,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,EAAE,KAAK,CAAC;8CACpC,CAAC,CAAC,IAAI;iDACH,CAAC,CAAC,OAAO,IAAI,GAAG;;4DAEL,EAAE,CAAC,KAAK;4DACR,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,KAAK;;;qBAG1D,CAAA;YACH,CAAC,CAAC;;;;WAIT,CAAA;QACH,CAAC,CAAC;;;;QAIF,CAAC,GAAG,EAAE;YACN,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO;iBAC7B,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;iBAC5B,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;iBAC5D,MAAM,CAAC,CAAC,EAAE,KAAK,EAAO,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;YAC/C,OAAO,IAAI,CAAA;;oDAEiC,WAAW,CAAC,MAAM;;gBAEtD,WAAW,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAO,EAAE,EAAE,CAAC,IAAI,CAAA;kDACT,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,EAAE,QAAQ,CAAC;6CACvC,CAAC,CAAC,IAAI;;sBAE7B,CAAC,CAAC,IAAI,IAAI,GAAG;sBACb,CAAC,CAAC,WAAW,KAAK,UAAU,IAAI,CAAC,CAAC,MAAM;gBACxC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,MAAM,CAAC;oBAC9B,CAAC,CAAC,IAAI,CAAA,YAAY,oBAAoB,CAAC,CAAC,CAAC,MAAM,CAAC;iFACS,CAAC,CAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,eAAe,EAAE;6BACrF,CAAC,CAAC,MAAM,QAAQ;oBACrB,CAAC,CAAC,CAAC,CAAC,MAAM;gBACZ,CAAC,CAAC,CAAC,CAAC,WAAW,IAAI,QAAQ;;+CAEF,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;;eAEhD,CAAC;;;SAGP,CAAA;QACH,CAAC,CAAC,EAAE;;;;;;;yEAO+D,IAAI,CAAC,MAAM;;;;yEAIX,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC,MAAM;;;;yEAI1D,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,IAAI,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,MAAM;;;;yEAI1F,IAAI,CAAC,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,UAAU,CAAC,CAAC,MAAM;;;;;;;QAO3H,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC,CAAC,CAAC,OAAO;KAC5D,CAAA;IACH,CAAC;IAEO,kBAAkB;QACxB,MAAM,CAAC,GAAG,IAAI,CAAC,cAAc,CAAA;QAC7B,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,KAAK,KAAK,CAAA;QAE/B,OAAO,IAAI,CAAA;0CAC2B,GAAG,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE;wCAC1B,CAAC,CAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,eAAe,EAAE;;oBAErD,CAAC,CAAC,IAAI;iDACuB,GAAG,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE;;;YAG7D,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAA;;;0CAGU,CAAC,CAAC,WAAW;;WAE5C,CAAC,CAAC,CAAC,EAAE;;YAEJ,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA;cACV,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAA;;2CAEa,CAAC,CAAC,OAAO;aACvC,CAAC,CAAC,CAAC,EAAE;;cAEJ,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAA;;;yDAGyB,CAAC,iBAAiB,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,IAAI,MAAM;oBAC3F,CAAC,iBAAiB,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,SAAS;;;aAGlE,CAAC,CAAC,CAAC,EAAE;;cAEJ,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAA;;;yDAGyB,CAAC,iBAAiB,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,IAAI,MAAM;oBAC3F,CAAC,iBAAiB,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,SAAS;;;aAGvH,CAAC,CAAC,CAAC,EAAE;;cAEJ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAA;;;4CAGe,CAAC,CAAC,MAAM;;aAEvC,CAAC,CAAC,CAAC,EAAE;;;gDAG8B,GAAG,EAAE,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAA,CAAC,CAAC;;;;gDAItD,GAAG,EAAE,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAA,CAAC,CAAC;;;;WAIxF,CAAC,CAAC,CAAC,IAAI,CAAA;;;0CAGwB,CAAC,CAAC,IAAI,IAAI,GAAG;;;;0CAIb,CAAC,CAAC,WAAW,IAAI,QAAQ;;;;;kBAKjD,CAAC,CAAC,MAAM,IAAI,oBAAoB,CAAC,CAAC,CAAC,MAAM,CAAC;YAC1C,CAAC,CAAC,IAAI,CAAA,YAAY,oBAAoB,CAAC,CAAC,CAAC,MAAM,CAAC,2DAA2D,CAAC,CAAC,MAAM,QAAQ;YAC3H,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,KAAK;;;;;;gBAMrB,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CACtC,IAAI,CAAA,qEAAqE,GAAG,QAAQ,CACrF;gBACC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA,sDAAsD,CAAC,CAAC,CAAC,EAAE;;;;gDAI1E,GAAG,EAAE,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,QAAQ,CAAC,uBAAuB,CAAC,CAAA,CAAC,CAAC;;;;WAIpG;;;KAGN,CAAA;IACH,CAAC;;AA1cM,qBAAM,GAAG;IACd,eAAe;IACf,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAwLF;CACF,AA3LY,CA2LZ;AAEQ;IAAR,KAAK,EAAE;;+CAAoB;AACnB;IAAR,KAAK,EAAE;;+CAAoB;AACnB;IAAR,KAAK,EAAE;;+CAAe;AACd;IAAR,KAAK,EAAE;;sDAA2B;AAjMxB,cAAc;IAD1B,aAAa,CAAC,kBAAkB,CAAC;GACrB,cAAc,CA4c1B","sourcesContent":["import { html, css, nothing } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\nimport { PageView } from '@operato/shell'\nimport { ScrollbarStyles } from '@operato/styles'\nimport { client } from '@operato/graphql'\nimport { navigate } from '@operato/shell'\nimport gql from 'graphql-tag'\n\n/**\n * KPI 시스템 종합 안내 페이지\n *\n * DB에서 실시간으로 KPI 계층, 산식, 메트릭, scoreType/valueType 등을 조회하여\n * 시스템 전체 구조를 한눈에 파악할 수 있는 매뉴얼 겸 라이브 대시보드.\n */\n\nconst GET_KPI_SYSTEM = gql`\n query {\n kpiRoot: kpisLevel1 {\n id\n name\n description\n formula\n weight\n scoreType\n valueType\n active\n kpis: children {\n id\n name\n description\n formula\n weight\n scoreType\n valueType\n active\n grades\n }\n }\n kpiMetrics {\n items {\n id\n name\n unit\n source\n collectType\n active\n }\n }\n }\n`\n\nconst SCORE_TYPE_LABELS: Record<string, { label: string; color: string }> = {\n DIRECT: { label: 'DIRECT (변환 없음)', color: '#888' },\n FORMULA: { label: 'FORMULA (수식 변환)', color: '#9c27b0' },\n LOOKUP: { label: 'LOOKUP (등급표)', color: '#1976d2' },\n CUSTOM: { label: 'CUSTOM (2D 룩업)', color: '#e65100' }\n}\n\n/** 외부 시스템 URL 매핑 */\nconst EXTERNAL_SYSTEM_URLS: Record<string, string> = {\n '세움터(EAIS)': 'https://cloud.eais.go.kr',\n '키스콘(KISCON)': 'https://www.kiscon.net',\n '올바로(Allbaro)': 'https://www.allbaro.or.kr',\n '전자카드제': 'https://www.cw.or.kr'\n}\n\nconst VALUE_TYPE_LABELS: Record<string, { label: string; color: string; icon: string }> = {\n MEASURED: { label: '외부 수집', color: '#1976d2', icon: '📡' },\n ASSESSED: { label: '감리자 평가', color: '#2e7d32', icon: '✍️' },\n CALCULATED: { label: '산식 계산', color: '#9c27b0', icon: '🔢' },\n COMPOSITE: { label: '복합 입력', color: '#e65100', icon: '🔀' }\n}\n\n@customElement('kpi-system-guide')\nexport class KpiSystemGuide extends PageView {\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: block;\n overflow-y: auto;\n background: #f5f6fa;\n padding: 24px;\n }\n\n .page-title {\n font-size: 1.5rem;\n font-weight: 800;\n color: #1a237e;\n margin-bottom: 4px;\n }\n .page-subtitle {\n font-size: 0.9rem;\n color: #666;\n margin-bottom: 24px;\n }\n\n /* 계층 트리 */\n .tree-section {\n background: #fff;\n border-radius: 12px;\n padding: 24px;\n margin-bottom: 24px;\n box-shadow: 0 2px 8px rgba(0,0,0,0.06);\n }\n .section-title {\n font-size: 1.1rem;\n font-weight: 700;\n color: #333;\n margin-bottom: 16px;\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n /* Z 루트 */\n .z-node {\n background: linear-gradient(135deg, #1a237e, #283593);\n color: #fff;\n border-radius: 10px;\n padding: 16px 20px;\n margin-bottom: 16px;\n cursor: pointer;\n }\n .z-node:hover { opacity: 0.95; }\n .z-name { font-size: 1.1rem; font-weight: 700; }\n .z-formula { font-size: 0.8rem; opacity: 0.8; margin-top: 4px; font-family: monospace; }\n\n /* Y 그룹 */\n .y-group {\n margin-bottom: 16px;\n border: 1px solid #e0e0e0;\n border-radius: 10px;\n overflow: hidden;\n }\n .y-header {\n background: linear-gradient(135deg, #0c4da2, #1565c0);\n color: #fff;\n padding: 12px 16px;\n display: flex;\n justify-content: space-between;\n align-items: center;\n cursor: pointer;\n }\n .y-header:hover { opacity: 0.95; }\n .y-name { font-weight: 700; font-size: 1rem; }\n .y-weight {\n background: rgba(255,255,255,0.2);\n padding: 2px 10px;\n border-radius: 12px;\n font-size: 0.8rem;\n }\n .y-body { padding: 12px; }\n .y-formula {\n font-size: 0.8rem;\n color: #555;\n background: #f8f9fa;\n padding: 8px 12px;\n border-radius: 6px;\n font-family: monospace;\n margin-bottom: 10px;\n word-break: break-all;\n }\n\n /* X 카드 */\n .x-cards { display: flex; flex-wrap: wrap; gap: 8px; }\n .x-card {\n flex: 1 1 220px;\n background: #fff;\n border: 1px solid #e8e8e8;\n border-radius: 8px;\n padding: 10px 14px;\n cursor: pointer;\n transition: all 0.2s;\n min-width: 200px;\n }\n .x-card:hover {\n border-color: #1976d2;\n box-shadow: 0 2px 8px rgba(25,118,210,0.15);\n }\n .x-name { font-weight: 600; font-size: 0.85rem; color: #333; margin-bottom: 4px; }\n .x-formula {\n font-size: 0.75rem; color: #888; font-family: monospace;\n white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\n margin-bottom: 6px;\n }\n .x-badges { display: flex; gap: 4px; flex-wrap: wrap; }\n .badge {\n font-size: 0.65rem;\n padding: 2px 6px;\n border-radius: 4px;\n font-weight: 600;\n }\n .badge-score { background: #e3f2fd; color: #1565c0; }\n .badge-value { background: #e8f5e9; color: #2e7d32; }\n\n /* 메트릭 섹션 */\n .metrics-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));\n gap: 10px;\n }\n .metric-card {\n background: #fff;\n border: 1px solid #e8e8e8;\n border-radius: 8px;\n padding: 10px 14px;\n transition: all 0.2s;\n }\n .metric-card:hover { border-color: #4caf50; }\n .metric-name { font-weight: 600; font-size: 0.85rem; color: #333; }\n .metric-unit { font-size: 0.75rem; color: #888; }\n .metric-used {\n font-size: 0.7rem; color: #1976d2; margin-top: 4px;\n }\n\n /* 팝업 */\n .popup-overlay {\n position: fixed; top: 0; left: 0; right: 0; bottom: 0;\n background: rgba(0,0,0,0.3); z-index: 1000;\n display: flex; align-items: center; justify-content: center;\n }\n .popup-box {\n background: #fff; border-radius: 12px; padding: 24px;\n min-width: 400px; max-width: 600px; max-height: 80vh;\n overflow-y: auto; box-shadow: 0 8px 32px rgba(0,0,0,0.2);\n }\n .popup-title {\n font-size: 1.1rem; font-weight: 700; color: #1a237e;\n margin-bottom: 16px; display: flex; justify-content: space-between;\n }\n .popup-close {\n background: none; border: none; font-size: 1.2rem;\n cursor: pointer; color: #999;\n }\n .popup-row {\n display: flex; gap: 8px; margin-bottom: 8px;\n font-size: 0.85rem;\n }\n .popup-label { color: #888; min-width: 100px; flex-shrink: 0; }\n .popup-value { color: #333; font-weight: 500; }\n .popup-formula {\n background: #f5f5f5; padding: 10px; border-radius: 6px;\n font-family: monospace; font-size: 0.85rem; margin: 8px 0;\n word-break: break-all;\n }\n .popup-link {\n color: #1976d2; cursor: pointer; text-decoration: underline;\n font-size: 0.85rem;\n }\n .popup-link:hover { color: #0d47a1; }\n\n /* 범례 */\n .legend {\n display: flex; gap: 16px; flex-wrap: wrap;\n margin-bottom: 20px; padding: 12px 16px;\n background: #fff; border-radius: 8px;\n box-shadow: 0 1px 4px rgba(0,0,0,0.04);\n }\n .legend-group { display: flex; align-items: center; gap: 4px; font-size: 0.8rem; }\n .legend-title { font-weight: 700; color: #555; margin-right: 4px; }\n `\n ]\n\n @state() kpiRoot: any[] = []\n @state() metrics: any[] = []\n @state() loading = true\n @state() selectedDetail: any = null\n\n get context() {\n return { title: 'KPI 시스템 가이드' }\n }\n\n async firstUpdated() {\n try {\n const { data } = await client.query({ query: GET_KPI_SYSTEM })\n this.kpiRoot = data.kpiRoot || []\n this.metrics = data.kpiMetrics?.items || []\n } catch (e) {\n console.error('Failed to load KPI system data:', e)\n } finally {\n this.loading = false\n }\n }\n\n /** 메트릭이 사용되는 KPI 목록 — 비활성 KPI/Y 그룹 제외 */\n private getMetricUsage(metricName: string): string[] {\n const used: string[] = []\n for (const y of this.kpiRoot) {\n if (y.active === false) continue\n for (const x of (y.kpis || [])) {\n if (x.active === false) continue\n if (x.formula?.includes(`[${metricName}]`)) {\n used.push(x.name)\n }\n }\n }\n return used\n }\n\n private showDetail(item: any, type: 'kpi' | 'metric') {\n this.selectedDetail = { ...item, _type: type }\n }\n\n private closeDetail() {\n this.selectedDetail = null\n }\n\n render() {\n if (this.loading) return html`<div style=\"padding:40px;text-align:center;color:#888;\">로딩 중...</div>`\n\n // Z 노드 (루트의 부모)\n const allX = this.kpiRoot.flatMap((y: any) => (y.kpis || []).filter((x: any) => x.active !== false))\n\n return html`\n <div class=\"page-title\">KPI 시스템 가이드</div>\n <div class=\"page-subtitle\">\n 건축현장 성과평가 KPI 체계의 전체 구조, 산식, 메트릭 연관관계를 확인합니다.\n 모든 정보는 DB에서 실시간 조회됩니다.\n </div>\n\n <!-- 범례 -->\n <div class=\"legend\">\n <span class=\"legend-title\">Score 산정:</span>\n ${Object.entries(SCORE_TYPE_LABELS).map(([, v]) =>\n html`<span class=\"legend-group\"><span class=\"badge badge-score\">${v.label}</span></span>`\n )}\n <span style=\"width:16px\"></span>\n <span class=\"legend-title\">Value 획득:</span>\n ${Object.entries(VALUE_TYPE_LABELS).map(([, v]) =>\n html`<span class=\"legend-group\">${v.icon} <span class=\"badge badge-value\">${v.label}</span></span>`\n )}\n </div>\n\n <!-- 1. KPI 계층 구조 -->\n <div class=\"tree-section\">\n <div class=\"section-title\">📊 KPI 계층 구조 (Z → Y → X)</div>\n\n <!-- Z 루트 -->\n <div class=\"z-node\" @click=${() => this.showDetail({ name: 'Z. 전체스코어', description: 'Y1~Y6 가중합', formula: 'Y1×w1 + Y2×w2 + ... + Y6×w6', scoreType: 'DIRECT', valueType: 'CALCULATED' }, 'kpi')}>\n <div class=\"z-name\">Z. 전체스코어</div>\n <div class=\"z-formula\">= ${this.kpiRoot\n .filter((y: any) => y.active !== false)\n .map((y: any) => `${y.name.split('.')[0]}×${y.weight}`)\n .join(' + ')}</div>\n </div>\n\n <!-- Y 그룹들 (비활성 Y 제외) -->\n ${this.kpiRoot.filter((y: any) => y.active !== false).map((y: any) => {\n const activeKpis = (y.kpis || []).filter((x: any) => x.active !== false)\n return html`\n <div class=\"y-group\">\n <div class=\"y-header\" @click=${() => this.showDetail(y, 'kpi')}>\n <span class=\"y-name\">${y.name}</span>\n <span class=\"y-weight\">가중치: ${y.weight}</span>\n </div>\n <div class=\"y-body\">\n ${y.formula ? html`<div class=\"y-formula\">${y.formula}</div>` : ''}\n <div class=\"x-cards\">\n ${activeKpis.map((x: any) => {\n const st = SCORE_TYPE_LABELS[x.scoreType] || { label: x.scoreType || '미설정', color: '#999' }\n const vt = VALUE_TYPE_LABELS[x.valueType] || { label: x.valueType || '미설정', color: '#999', icon: '?' }\n return html`\n <div class=\"x-card\" @click=${() => this.showDetail(x, 'kpi')}>\n <div class=\"x-name\">${x.name}</div>\n <div class=\"x-formula\">${x.formula || '—'}</div>\n <div class=\"x-badges\">\n <span class=\"badge badge-score\">${st.label}</span>\n <span class=\"badge badge-value\">${vt.icon} ${vt.label}</span>\n </div>\n </div>\n `\n })}\n </div>\n </div>\n </div>\n `\n })}\n </div>\n\n <!-- 2. 원천 메트릭 — active=true 이면서 활성 KPI formula 에 실제 참조되는 것만 -->\n ${(() => {\n const usedMetrics = this.metrics\n .filter((m: any) => m.active)\n .map((m: any) => ({ m, usage: this.getMetricUsage(m.name) }))\n .filter(({ usage }: any) => usage.length > 0)\n return html`\n <div class=\"tree-section\">\n <div class=\"section-title\">📋 원천 메트릭 (${usedMetrics.length}개 사용)</div>\n <div class=\"metrics-grid\">\n ${usedMetrics.map(({ m, usage }: any) => html`\n <div class=\"metric-card\" @click=${() => this.showDetail(m, 'metric')}>\n <div class=\"metric-name\">${m.name}</div>\n <div class=\"metric-unit\">\n ${m.unit || '—'} ·\n ${m.collectType === 'EXTERNAL' && m.source\n ? EXTERNAL_SYSTEM_URLS[m.source]\n ? html`<a href=\"${EXTERNAL_SYSTEM_URLS[m.source]}\" target=\"_blank\" rel=\"noopener\"\n style=\"color:#1976d2;text-decoration:none;\" @click=${(e: Event) => e.stopPropagation()}\n >${m.source} ↗</a>`\n : m.source\n : m.collectType || 'MANUAL'}\n </div>\n <div class=\"metric-used\">→ ${usage.join(', ')}</div>\n </div>\n `)}\n </div>\n </div>\n `\n })()}\n\n <!-- 3. 시스템 요약 -->\n <div class=\"tree-section\">\n <div class=\"section-title\">📈 시스템 요약</div>\n <div style=\"display:flex;gap:16px;flex-wrap:wrap;\">\n <div style=\"flex:1;min-width:200px;background:#e3f2fd;padding:16px;border-radius:8px;text-align:center;\">\n <div style=\"font-size:2rem;font-weight:800;color:#1565c0;\">${allX.length}</div>\n <div style=\"font-size:0.85rem;color:#555;\">X-level 지표</div>\n </div>\n <div style=\"flex:1;min-width:200px;background:#e8f5e9;padding:16px;border-radius:8px;text-align:center;\">\n <div style=\"font-size:2rem;font-weight:800;color:#2e7d32;\">${this.kpiRoot.filter((y: any) => y.active !== false).length}</div>\n <div style=\"font-size:0.85rem;color:#555;\">Y-level 영역</div>\n </div>\n <div style=\"flex:1;min-width:200px;background:#fff3e0;padding:16px;border-radius:8px;text-align:center;\">\n <div style=\"font-size:2rem;font-weight:800;color:#e65100;\">${this.metrics.filter((m: any) => m.active && this.getMetricUsage(m.name).length > 0).length}</div>\n <div style=\"font-size:0.85rem;color:#555;\">원천 메트릭</div>\n </div>\n <div style=\"flex:1;min-width:200px;background:#fce4ec;padding:16px;border-radius:8px;text-align:center;\">\n <div style=\"font-size:2rem;font-weight:800;color:#c62828;\">${allX.filter((x: any) => x.valueType === 'ASSESSED').length}</div>\n <div style=\"font-size:0.85rem;color:#555;\">감리자 평가 항목</div>\n </div>\n </div>\n </div>\n\n <!-- 팝업 -->\n ${this.selectedDetail ? this._renderDetailPopup() : nothing}\n `\n }\n\n private _renderDetailPopup() {\n const d = this.selectedDetail\n const isKpi = d._type === 'kpi'\n\n return html`\n <div class=\"popup-overlay\" @click=${() => this.closeDetail()}>\n <div class=\"popup-box\" @click=${(e: Event) => e.stopPropagation()}>\n <div class=\"popup-title\">\n <span>${d.name}</span>\n <button class=\"popup-close\" @click=${() => this.closeDetail()}>×</button>\n </div>\n\n ${d.description ? html`\n <div class=\"popup-row\">\n <span class=\"popup-label\">설명</span>\n <span class=\"popup-value\">${d.description}</span>\n </div>\n ` : ''}\n\n ${isKpi ? html`\n ${d.formula ? html`\n <div class=\"popup-row\"><span class=\"popup-label\">산식</span></div>\n <div class=\"popup-formula\">${d.formula}</div>\n ` : ''}\n\n ${d.scoreType ? html`\n <div class=\"popup-row\">\n <span class=\"popup-label\">Score 산정</span>\n <span class=\"popup-value\" style=\"color:${(SCORE_TYPE_LABELS[d.scoreType] || {}).color || '#333'}\">\n ${(SCORE_TYPE_LABELS[d.scoreType] || {}).label || d.scoreType}\n </span>\n </div>\n ` : ''}\n\n ${d.valueType ? html`\n <div class=\"popup-row\">\n <span class=\"popup-label\">Value 획득</span>\n <span class=\"popup-value\" style=\"color:${(VALUE_TYPE_LABELS[d.valueType] || {}).color || '#333'}\">\n ${(VALUE_TYPE_LABELS[d.valueType] || {}).icon || ''} ${(VALUE_TYPE_LABELS[d.valueType] || {}).label || d.valueType}\n </span>\n </div>\n ` : ''}\n\n ${d.weight ? html`\n <div class=\"popup-row\">\n <span class=\"popup-label\">가중치</span>\n <span class=\"popup-value\">${d.weight}</span>\n </div>\n ` : ''}\n\n <div style=\"margin-top:16px;padding-top:12px;border-top:1px solid #eee;\">\n <span class=\"popup-link\" @click=${() => { this.closeDetail(); navigate('kpi-overview') }}>\n → KPI 개요에서 보기\n </span>\n &nbsp;&nbsp;\n <span class=\"popup-link\" @click=${() => { this.closeDetail(); navigate('kpi-admin') }}>\n → KPI 관리에서 편집\n </span>\n </div>\n ` : html`\n <div class=\"popup-row\">\n <span class=\"popup-label\">단위</span>\n <span class=\"popup-value\">${d.unit || '—'}</span>\n </div>\n <div class=\"popup-row\">\n <span class=\"popup-label\">수집 방식</span>\n <span class=\"popup-value\">${d.collectType || 'MANUAL'}</span>\n </div>\n <div class=\"popup-row\">\n <span class=\"popup-label\">데이터 소스</span>\n <span class=\"popup-value\">\n ${d.source && EXTERNAL_SYSTEM_URLS[d.source]\n ? html`<a href=\"${EXTERNAL_SYSTEM_URLS[d.source]}\" target=\"_blank\" rel=\"noopener\" style=\"color:#1976d2;\">${d.source} ↗</a>`\n : d.source || '미설정'}\n </span>\n </div>\n\n <div style=\"margin-top:12px;\">\n <div class=\"popup-label\" style=\"margin-bottom:6px;\">사용처:</div>\n ${this.getMetricUsage(d.name).map(kpi =>\n html`<div style=\"font-size:0.85rem;color:#1976d2;margin-bottom:2px;\">· ${kpi}</div>`\n )}\n ${this.getMetricUsage(d.name).length === 0 ? html`<div style=\"color:#ccc;font-size:0.85rem;\">미사용</div>` : ''}\n </div>\n\n <div style=\"margin-top:16px;padding-top:12px;border-top:1px solid #eee;\">\n <span class=\"popup-link\" @click=${() => { this.closeDetail(); navigate('kpi-metric-value-list') }}>\n → 메트릭 값 관리\n </span>\n </div>\n `}\n </div>\n </div>\n `\n }\n}\n"]}
1
+ {"version":3,"file":"kpi-system-guide.js","sourceRoot":"","sources":["../../../client/pages/kpi-admin/kpi-system-guide.ts"],"names":[],"mappings":"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","sourcesContent":["import { html, css, nothing } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\nimport { PageView } from '@operato/shell'\nimport { ScrollbarStyles } from '@operato/styles'\nimport { client } from '@operato/graphql'\nimport { navigate } from '@operato/shell'\nimport gql from 'graphql-tag'\n\n/**\n * KPI 시스템 종합 안내 페이지\n *\n * DB에서 실시간으로 KPI 계층, 산식, 메트릭, scoreType/valueType 등을 조회하여\n * 시스템 전체 구조를 한눈에 파악할 수 있는 매뉴얼 겸 라이브 대시보드.\n */\n\nconst GET_KPI_SYSTEM = gql`\n query {\n kpiRoot: kpisLevel1 {\n id\n name\n description\n formula\n weight\n scoreType\n valueType\n active\n kpis: children {\n id\n name\n description\n formula\n weight\n scoreType\n valueType\n active\n grades\n }\n }\n kpiMetrics {\n items {\n id\n name\n unit\n source\n collectType\n active\n }\n }\n }\n`\n\nconst SCORE_TYPE_LABELS: Record<string, { label: string; color: string }> = {\n DIRECT: { label: 'DIRECT (변환 없음)', color: '#888' },\n FORMULA: { label: 'FORMULA (수식 변환)', color: '#9c27b0' },\n LOOKUP: { label: 'LOOKUP (등급표)', color: '#1976d2' },\n CUSTOM: { label: 'CUSTOM (2D 룩업)', color: '#e65100' }\n}\n\n/** 외부 시스템 URL 매핑 */\nconst EXTERNAL_SYSTEM_URLS: Record<string, string> = {\n '세움터(EAIS)': 'https://cloud.eais.go.kr',\n '키스콘(KISCON)': 'https://www.kiscon.net',\n '올바로(Allbaro)': 'https://www.allbaro.or.kr',\n 전자카드제: 'https://www.cw.or.kr'\n}\n\nconst VALUE_TYPE_LABELS: Record<string, { label: string; color: string; icon: string }> = {\n MEASURED: { label: '외부 수집', color: '#1976d2', icon: '📡' },\n ASSESSED: { label: '감리자 평가', color: '#2e7d32', icon: '✍️' },\n CALCULATED: { label: '산식 계산', color: '#9c27b0', icon: '🔢' },\n COMPOSITE: { label: '복합 입력', color: '#e65100', icon: '🔀' }\n}\n\n@customElement('kpi-system-guide')\nexport class KpiSystemGuide extends PageView {\n static styles = [\n ScrollbarStyles,\n css`\n :host {\n display: block;\n overflow-y: auto;\n background: #f5f6fa;\n padding: 24px;\n }\n\n .page-title {\n font-size: 1.5rem;\n font-weight: 800;\n color: #1a237e;\n margin-bottom: 4px;\n }\n .page-subtitle {\n font-size: 0.9rem;\n color: #666;\n margin-bottom: 24px;\n }\n\n /* 계층 트리 */\n .tree-section {\n background: #fff;\n border-radius: 12px;\n padding: 24px;\n margin-bottom: 24px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);\n }\n .section-title {\n font-size: 1.1rem;\n font-weight: 700;\n color: #333;\n margin-bottom: 16px;\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n /* Z 루트 */\n .z-node {\n background: linear-gradient(135deg, #1a237e, #283593);\n color: #fff;\n border-radius: 10px;\n padding: 16px 20px;\n margin-bottom: 16px;\n cursor: pointer;\n }\n .z-node:hover {\n opacity: 0.95;\n }\n .z-name {\n font-size: 1.1rem;\n font-weight: 700;\n }\n .z-formula {\n font-size: 0.8rem;\n opacity: 0.8;\n margin-top: 4px;\n font-family: monospace;\n }\n\n /* Y 그룹 */\n .y-group {\n margin-bottom: 16px;\n border: 1px solid #e0e0e0;\n border-radius: 10px;\n overflow: hidden;\n }\n .y-header {\n background: linear-gradient(135deg, #0c4da2, #1565c0);\n color: #fff;\n padding: 12px 16px;\n display: flex;\n justify-content: space-between;\n align-items: center;\n cursor: pointer;\n }\n .y-header:hover {\n opacity: 0.95;\n }\n .y-name {\n font-weight: 700;\n font-size: 1rem;\n }\n .y-weight {\n background: rgba(255, 255, 255, 0.2);\n padding: 2px 10px;\n border-radius: 12px;\n font-size: 0.8rem;\n }\n .y-body {\n padding: 12px;\n }\n .y-formula {\n font-size: 0.8rem;\n color: #555;\n background: #f8f9fa;\n padding: 8px 12px;\n border-radius: 6px;\n font-family: monospace;\n margin-bottom: 10px;\n word-break: break-all;\n }\n\n /* X 카드 */\n .x-cards {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n }\n .x-card {\n flex: 1 1 220px;\n background: #fff;\n border: 1px solid #e8e8e8;\n border-radius: 8px;\n padding: 10px 14px;\n cursor: pointer;\n transition: all 0.2s;\n min-width: 200px;\n }\n .x-card:hover {\n border-color: #1976d2;\n box-shadow: 0 2px 8px rgba(25, 118, 210, 0.15);\n }\n .x-name {\n font-weight: 600;\n font-size: 0.85rem;\n color: #333;\n margin-bottom: 4px;\n }\n .x-formula {\n font-size: 0.75rem;\n color: #888;\n font-family: monospace;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n margin-bottom: 6px;\n }\n .x-badges {\n display: flex;\n gap: 4px;\n flex-wrap: wrap;\n }\n .badge {\n font-size: 0.65rem;\n padding: 2px 6px;\n border-radius: 4px;\n font-weight: 600;\n }\n .badge-score {\n background: #e3f2fd;\n color: #1565c0;\n }\n .badge-value {\n background: #e8f5e9;\n color: #2e7d32;\n }\n\n /* 메트릭 섹션 */\n .metrics-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));\n gap: 10px;\n }\n .metric-card {\n background: #fff;\n border: 1px solid #e8e8e8;\n border-radius: 8px;\n padding: 10px 14px;\n transition: all 0.2s;\n }\n .metric-card:hover {\n border-color: #4caf50;\n }\n .metric-name {\n font-weight: 600;\n font-size: 0.85rem;\n color: #333;\n }\n .metric-unit {\n font-size: 0.75rem;\n color: #888;\n }\n .metric-used {\n font-size: 0.7rem;\n color: #1976d2;\n margin-top: 4px;\n }\n\n /* 팝업 */\n .popup-overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.3);\n z-index: 1000;\n display: flex;\n align-items: center;\n justify-content: center;\n }\n .popup-box {\n background: #fff;\n border-radius: 12px;\n padding: 24px;\n min-width: 400px;\n max-width: 600px;\n max-height: 80vh;\n overflow-y: auto;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);\n }\n .popup-title {\n font-size: 1.1rem;\n font-weight: 700;\n color: #1a237e;\n margin-bottom: 16px;\n display: flex;\n justify-content: space-between;\n }\n .popup-close {\n background: none;\n border: none;\n font-size: 1.2rem;\n cursor: pointer;\n color: #999;\n }\n .popup-row {\n display: flex;\n gap: 8px;\n margin-bottom: 8px;\n font-size: 0.85rem;\n }\n .popup-label {\n color: #888;\n min-width: 100px;\n flex-shrink: 0;\n }\n .popup-value {\n color: #333;\n font-weight: 500;\n }\n .popup-formula {\n background: #f5f5f5;\n padding: 10px;\n border-radius: 6px;\n font-family: monospace;\n font-size: 0.85rem;\n margin: 8px 0;\n word-break: break-all;\n }\n .popup-link {\n color: #1976d2;\n cursor: pointer;\n text-decoration: underline;\n font-size: 0.85rem;\n }\n .popup-link:hover {\n color: #0d47a1;\n }\n\n /* 범례 */\n .legend {\n display: flex;\n gap: 16px;\n flex-wrap: wrap;\n margin-bottom: 20px;\n padding: 12px 16px;\n background: #fff;\n border-radius: 8px;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);\n }\n .legend-group {\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 0.8rem;\n }\n .legend-title {\n font-weight: 700;\n color: #555;\n margin-right: 4px;\n }\n `\n ]\n\n @state() kpiRoot: any[] = []\n @state() metrics: any[] = []\n @state() loading = true\n @state() selectedDetail: any = null\n\n get context() {\n return { title: 'KPI 시스템 가이드' }\n }\n\n async firstUpdated() {\n try {\n const { data } = await client.query({ query: GET_KPI_SYSTEM })\n this.kpiRoot = data.kpiRoot || []\n this.metrics = data.kpiMetrics?.items || []\n } catch (e) {\n console.error('Failed to load KPI system data:', e)\n } finally {\n this.loading = false\n }\n }\n\n /** 메트릭이 사용되는 KPI 목록 — 비활성 KPI/Y 그룹 제외 */\n private getMetricUsage(metricName: string): string[] {\n const used: string[] = []\n for (const y of this.kpiRoot) {\n if (y.active === false) continue\n for (const x of y.kpis || []) {\n if (x.active === false) continue\n if (x.formula?.includes(`[${metricName}]`)) {\n used.push(x.name)\n }\n }\n }\n return used\n }\n\n private showDetail(item: any, type: 'kpi' | 'metric') {\n this.selectedDetail = { ...item, _type: type }\n }\n\n private closeDetail() {\n this.selectedDetail = null\n }\n\n render() {\n if (this.loading) return html`<div style=\"padding:40px;text-align:center;color:#888;\">로딩 중...</div>`\n\n // Z 노드 (루트의 부모)\n const allX = this.kpiRoot.flatMap((y: any) => (y.kpis || []).filter((x: any) => x.active !== false))\n\n return html`\n <div class=\"page-title\">KPI 시스템 가이드</div>\n <div class=\"page-subtitle\">\n 건축프로젝트 성과평가 KPI 체계의 전체 구조, 산식, 메트릭 연관관계를 확인합니다. 모든 정보는 DB에서 실시간 조회됩니다.\n </div>\n\n <!-- 범례 -->\n <div class=\"legend\">\n <span class=\"legend-title\">Score 산정:</span>\n ${Object.entries(SCORE_TYPE_LABELS).map(\n ([, v]) => html`<span class=\"legend-group\"><span class=\"badge badge-score\">${v.label}</span></span>`\n )}\n <span style=\"width:16px\"></span>\n <span class=\"legend-title\">Value 획득:</span>\n ${Object.entries(VALUE_TYPE_LABELS).map(\n ([, v]) => html`<span class=\"legend-group\">${v.icon} <span class=\"badge badge-value\">${v.label}</span></span>`\n )}\n </div>\n\n <!-- 1. KPI 계층 구조 -->\n <div class=\"tree-section\">\n <div class=\"section-title\">📊 KPI 계층 구조 (Z → Y → X)</div>\n\n <!-- Z 루트 -->\n <div\n class=\"z-node\"\n @click=${() =>\n this.showDetail(\n {\n name: 'Z. 전체스코어',\n description: 'Y1~Y6 가중합',\n formula: 'Y1×w1 + Y2×w2 + ... + Y6×w6',\n scoreType: 'DIRECT',\n valueType: 'CALCULATED'\n },\n 'kpi'\n )}\n >\n <div class=\"z-name\">Z. 전체스코어</div>\n <div class=\"z-formula\">\n =\n ${this.kpiRoot\n .filter((y: any) => y.active !== false)\n .map((y: any) => `${y.name.split('.')[0]}×${y.weight}`)\n .join(' + ')}\n </div>\n </div>\n\n <!-- Y 그룹들 (비활성 Y 제외) -->\n ${this.kpiRoot\n .filter((y: any) => y.active !== false)\n .map((y: any) => {\n const activeKpis = (y.kpis || []).filter((x: any) => x.active !== false)\n return html`\n <div class=\"y-group\">\n <div class=\"y-header\" @click=${() => this.showDetail(y, 'kpi')}>\n <span class=\"y-name\">${y.name}</span>\n <span class=\"y-weight\">가중치: ${y.weight}</span>\n </div>\n <div class=\"y-body\">\n ${y.formula ? html`<div class=\"y-formula\">${y.formula}</div>` : ''}\n <div class=\"x-cards\">\n ${activeKpis.map((x: any) => {\n const st = SCORE_TYPE_LABELS[x.scoreType] || { label: x.scoreType || '미설정', color: '#999' }\n const vt = VALUE_TYPE_LABELS[x.valueType] || { label: x.valueType || '미설정', color: '#999', icon: '?' }\n return html`\n <div class=\"x-card\" @click=${() => this.showDetail(x, 'kpi')}>\n <div class=\"x-name\">${x.name}</div>\n <div class=\"x-formula\">${x.formula || '—'}</div>\n <div class=\"x-badges\">\n <span class=\"badge badge-score\">${st.label}</span>\n <span class=\"badge badge-value\">${vt.icon} ${vt.label}</span>\n </div>\n </div>\n `\n })}\n </div>\n </div>\n </div>\n `\n })}\n </div>\n\n <!-- 2. 원천 메트릭 — active=true 이면서 활성 KPI formula 에 실제 참조되는 것만 -->\n ${(() => {\n const usedMetrics = this.metrics\n .filter((m: any) => m.active)\n .map((m: any) => ({ m, usage: this.getMetricUsage(m.name) }))\n .filter(({ usage }: any) => usage.length > 0)\n return html`\n <div class=\"tree-section\">\n <div class=\"section-title\">📋 원천 메트릭 (${usedMetrics.length}개 사용)</div>\n <div class=\"metrics-grid\">\n ${usedMetrics.map(\n ({ m, usage }: any) => html`\n <div class=\"metric-card\" @click=${() => this.showDetail(m, 'metric')}>\n <div class=\"metric-name\">${m.name}</div>\n <div class=\"metric-unit\">\n ${m.unit || '—'} ·\n ${m.collectType === 'EXTERNAL' && m.source\n ? EXTERNAL_SYSTEM_URLS[m.source]\n ? html`<a\n href=\"${EXTERNAL_SYSTEM_URLS[m.source]}\"\n target=\"_blank\"\n rel=\"noopener\"\n style=\"color:#1976d2;text-decoration:none;\"\n @click=${(e: Event) => e.stopPropagation()}\n >${m.source} ↗</a\n >`\n : m.source\n : m.collectType || 'MANUAL'}\n </div>\n <div class=\"metric-used\">→ ${usage.join(', ')}</div>\n </div>\n `\n )}\n </div>\n </div>\n `\n })()}\n\n <!-- 3. 시스템 요약 -->\n <div class=\"tree-section\">\n <div class=\"section-title\">📈 시스템 요약</div>\n <div style=\"display:flex;gap:16px;flex-wrap:wrap;\">\n <div style=\"flex:1;min-width:200px;background:#e3f2fd;padding:16px;border-radius:8px;text-align:center;\">\n <div style=\"font-size:2rem;font-weight:800;color:#1565c0;\">${allX.length}</div>\n <div style=\"font-size:0.85rem;color:#555;\">X-level 지표</div>\n </div>\n <div style=\"flex:1;min-width:200px;background:#e8f5e9;padding:16px;border-radius:8px;text-align:center;\">\n <div style=\"font-size:2rem;font-weight:800;color:#2e7d32;\">\n ${this.kpiRoot.filter((y: any) => y.active !== false).length}\n </div>\n <div style=\"font-size:0.85rem;color:#555;\">Y-level 영역</div>\n </div>\n <div style=\"flex:1;min-width:200px;background:#fff3e0;padding:16px;border-radius:8px;text-align:center;\">\n <div style=\"font-size:2rem;font-weight:800;color:#e65100;\">\n ${this.metrics.filter((m: any) => m.active && this.getMetricUsage(m.name).length > 0).length}\n </div>\n <div style=\"font-size:0.85rem;color:#555;\">원천 메트릭</div>\n </div>\n <div style=\"flex:1;min-width:200px;background:#fce4ec;padding:16px;border-radius:8px;text-align:center;\">\n <div style=\"font-size:2rem;font-weight:800;color:#c62828;\">\n ${allX.filter((x: any) => x.valueType === 'ASSESSED').length}\n </div>\n <div style=\"font-size:0.85rem;color:#555;\">감리자 평가 항목</div>\n </div>\n </div>\n </div>\n\n <!-- 팝업 -->\n ${this.selectedDetail ? this._renderDetailPopup() : nothing}\n `\n }\n\n private _renderDetailPopup() {\n const d = this.selectedDetail\n const isKpi = d._type === 'kpi'\n\n return html`\n <div class=\"popup-overlay\" @click=${() => this.closeDetail()}>\n <div class=\"popup-box\" @click=${(e: Event) => e.stopPropagation()}>\n <div class=\"popup-title\">\n <span>${d.name}</span>\n <button class=\"popup-close\" @click=${() => this.closeDetail()}>×</button>\n </div>\n\n ${d.description\n ? html`\n <div class=\"popup-row\">\n <span class=\"popup-label\">설명</span>\n <span class=\"popup-value\">${d.description}</span>\n </div>\n `\n : ''}\n ${isKpi\n ? html`\n ${d.formula\n ? html`\n <div class=\"popup-row\"><span class=\"popup-label\">산식</span></div>\n <div class=\"popup-formula\">${d.formula}</div>\n `\n : ''}\n ${d.scoreType\n ? html`\n <div class=\"popup-row\">\n <span class=\"popup-label\">Score 산정</span>\n <span class=\"popup-value\" style=\"color:${(SCORE_TYPE_LABELS[d.scoreType] || {}).color || '#333'}\">\n ${(SCORE_TYPE_LABELS[d.scoreType] || {}).label || d.scoreType}\n </span>\n </div>\n `\n : ''}\n ${d.valueType\n ? html`\n <div class=\"popup-row\">\n <span class=\"popup-label\">Value 획득</span>\n <span class=\"popup-value\" style=\"color:${(VALUE_TYPE_LABELS[d.valueType] || {}).color || '#333'}\">\n ${(VALUE_TYPE_LABELS[d.valueType] || {}).icon || ''}\n ${(VALUE_TYPE_LABELS[d.valueType] || {}).label || d.valueType}\n </span>\n </div>\n `\n : ''}\n ${d.weight\n ? html`\n <div class=\"popup-row\">\n <span class=\"popup-label\">가중치</span>\n <span class=\"popup-value\">${d.weight}</span>\n </div>\n `\n : ''}\n\n <div style=\"margin-top:16px;padding-top:12px;border-top:1px solid #eee;\">\n <span\n class=\"popup-link\"\n @click=${() => {\n this.closeDetail()\n navigate('kpi-overview')\n }}\n >\n → KPI 개요에서 보기\n </span>\n &nbsp;&nbsp;\n <span\n class=\"popup-link\"\n @click=${() => {\n this.closeDetail()\n navigate('kpi-admin')\n }}\n >\n → KPI 관리에서 편집\n </span>\n </div>\n `\n : html`\n <div class=\"popup-row\">\n <span class=\"popup-label\">단위</span>\n <span class=\"popup-value\">${d.unit || '—'}</span>\n </div>\n <div class=\"popup-row\">\n <span class=\"popup-label\">수집 방식</span>\n <span class=\"popup-value\">${d.collectType || 'MANUAL'}</span>\n </div>\n <div class=\"popup-row\">\n <span class=\"popup-label\">데이터 소스</span>\n <span class=\"popup-value\">\n ${d.source && EXTERNAL_SYSTEM_URLS[d.source]\n ? html`<a href=\"${EXTERNAL_SYSTEM_URLS[d.source]}\" target=\"_blank\" rel=\"noopener\" style=\"color:#1976d2;\"\n >${d.source} ↗</a\n >`\n : d.source || '미설정'}\n </span>\n </div>\n\n <div style=\"margin-top:12px;\">\n <div class=\"popup-label\" style=\"margin-bottom:6px;\">사용처:</div>\n ${this.getMetricUsage(d.name).map(\n kpi => html`<div style=\"font-size:0.85rem;color:#1976d2;margin-bottom:2px;\">· ${kpi}</div>`\n )}\n ${this.getMetricUsage(d.name).length === 0 ? html`<div style=\"color:#ccc;font-size:0.85rem;\">미사용</div>` : ''}\n </div>\n\n <div style=\"margin-top:16px;padding-top:12px;border-top:1px solid #eee;\">\n <span\n class=\"popup-link\"\n @click=${() => {\n this.closeDetail()\n navigate('kpi-metric-value-list')\n }}\n >\n → 메트릭 값 관리\n </span>\n </div>\n `}\n </div>\n </div>\n `\n }\n}\n"]}
@@ -27,6 +27,12 @@ export declare class KpiLeftPanel extends LitElement {
27
27
  private calculateChangeRate;
28
28
  private generateChartData;
29
29
  private _onAllPeriodChange;
30
+ /** 시작월 설정 — 시작 > 종료(역전)면 종료를 시작에 맞춰 보정. (YYYY-MM 문자열 비교 = 시간순) */
31
+ private _setStartYearMonth;
32
+ /** 종료월 설정 — 종료 < 시작(역전)이면 시작을 종료에 맞춰 보정. */
33
+ private _setEndYearMonth;
34
+ /** 기간 변경을 부모로 전파 → 지역 팝업 등 다른 패널이 동일 기간을 쓰도록 동기화 */
35
+ private _emitPeriodChange;
30
36
  private onCategoryChange;
31
37
  private onChartTypeChange;
32
38
  private onRegionClick;
@@ -128,12 +128,12 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
128
128
  // selectedCategory에 따라 kpiName 결정
129
129
  const categoryToKpiName = {
130
130
  '전체 KPI': null,
131
- '일정 성과': 'Y1. 일정성과',
132
- '비용 성과': 'Y2. 비용성과',
133
- '품질 성과': 'Y3. 품질성과',
131
+ '일정 성과': '일정성과(Y1)',
132
+ '비용 성과': '비용성과(Y2)',
133
+ '품질 성과': '품질성과(Y3)',
134
134
  '안전 성과': 'Y4. 안전성과',
135
- '환경 성과': 'Y5. 환경성과',
136
- '생산성 성과': 'Y6. 생산성성과'
135
+ '환경 성과': '환경성과(Y5)',
136
+ '생산성 성과': '생산성성과(Y6)'
137
137
  };
138
138
  const kpiName = categoryToKpiName[this.selectedCategory];
139
139
  const response = await client.query({
@@ -186,12 +186,12 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
186
186
  // selectedCategory에 따라 kpiName 결정
187
187
  const categoryToKpiName = {
188
188
  '전체 KPI': null,
189
- '일정 성과': 'Y1. 일정성과',
190
- '비용 성과': 'Y2. 비용성과',
191
- '품질 성과': 'Y3. 품질성과',
189
+ '일정 성과': '일정성과(Y1)',
190
+ '비용 성과': '비용성과(Y2)',
191
+ '품질 성과': '품질성과(Y3)',
192
192
  '안전 성과': 'Y4. 안전성과',
193
- '환경 성과': 'Y5. 환경성과',
194
- '생산성 성과': 'Y6. 생산성성과'
193
+ '환경 성과': '환경성과(Y5)',
194
+ '생산성 성과': '생산성성과(Y6)'
195
195
  };
196
196
  const kpiName = categoryToKpiName[this.selectedCategory];
197
197
  const response = await client.query({
@@ -263,29 +263,31 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
263
263
  return ((latestValue - oldestValue) / oldestValue) * 100;
264
264
  }
265
265
  generateChartData() {
266
- // KPI 이름을 카테고리로 매핑 축약
267
- const categoryMapping = {
268
- 'Y1. 일정성과': '일정',
269
- 'Y2. 비용성과': '비용',
270
- 'Y3. 품질성과': '품질',
271
- 'Y4. 안전성과': '안전',
272
- 'Y5. 환경성과': '환경',
273
- 'Y6. 생산성성과': '생산성'
266
+ // kpiName 표기가 'Y1. 일정성과' / '일정성과(Y1)' 등으로 환경마다 달라 전체 문자열 매칭은 깨짐.
267
+ // Y1~Y6 코드만 추출해 짧은 라벨로 매핑.
268
+ const yLabelByCode = {
269
+ Y1: '일정',
270
+ Y2: '비용',
271
+ Y3: '품질',
272
+ Y4: '안전',
273
+ Y5: '환경',
274
+ Y6: '생산성'
274
275
  };
276
+ const toCategory = (kpiName) => { var _a, _b; return yLabelByCode[(_b = (_a = (kpiName || '').match(/Y[1-6]/)) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : ''] || kpiName; };
275
277
  if (this.selectedChartType === 'radar') {
276
278
  // 레이더 차트용 데이터 생성 (전체 평균만 표시)
277
279
  const data = [];
278
280
  const categories = [];
279
281
  this.kpiYComprehensiveStats.forEach(stat => {
280
- const category = categoryMapping[stat.kpiName] || stat.kpiName;
282
+ const category = toCategory(stat.kpiName);
281
283
  if (!categories.includes(category)) {
282
284
  categories.push(category);
283
285
  }
284
- // 전체 평균 데이터만 표시 (비교 시리즈 없음)
286
+ // 전체 평균 데이터만 표시 (비교 시리즈 없음) — 박스플롯과 동일 정밀도(Math.round 제거)
285
287
  data.push({
286
288
  org: '전체평균',
287
289
  category,
288
- value: Math.round(stat.avgVal * 100)
290
+ value: stat.avgVal * 100
289
291
  });
290
292
  });
291
293
  this.chartCategories = categories.length > 0 ? categories : ['일정', '비용', '품질', '안전', '환경', '생산성'];
@@ -296,7 +298,7 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
296
298
  const data = [];
297
299
  const categories = [];
298
300
  this.kpiYComprehensiveStats.forEach(stat => {
299
- const category = categoryMapping[stat.kpiName] || stat.kpiName;
301
+ const category = toCategory(stat.kpiName);
300
302
  if (!categories.includes(category)) {
301
303
  categories.push(category);
302
304
  }
@@ -318,9 +320,37 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
318
320
  _onAllPeriodChange(e) {
319
321
  this.isAllPeriod = e.target.checked;
320
322
  if (this.isAllPeriod) {
321
- this.startYearMonth = '';
322
- this.endYearMonth = '';
323
+ // 전체: 시작 = 선택 가능한 가장 이른 월(2022-01), 종료 = 현재월 → 모든 데이터 포함하며 표시도 명확
324
+ // (빈 문자열로 두면 드롭다운이 2022-01~2022-01 로 잘못 보이던 문제 방지)
325
+ const now = new Date();
326
+ this.startYearMonth = '2022-01';
327
+ this.endYearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
328
+ }
329
+ this._emitPeriodChange();
330
+ }
331
+ /** 시작월 설정 — 시작 > 종료(역전)면 종료를 시작에 맞춰 보정. (YYYY-MM 문자열 비교 = 시간순) */
332
+ _setStartYearMonth(ym) {
333
+ this.startYearMonth = ym;
334
+ if (this.endYearMonth && this.startYearMonth > this.endYearMonth) {
335
+ this.endYearMonth = this.startYearMonth;
323
336
  }
337
+ this._emitPeriodChange();
338
+ }
339
+ /** 종료월 설정 — 종료 < 시작(역전)이면 시작을 종료에 맞춰 보정. */
340
+ _setEndYearMonth(ym) {
341
+ this.endYearMonth = ym;
342
+ if (this.startYearMonth && this.endYearMonth < this.startYearMonth) {
343
+ this.startYearMonth = this.endYearMonth;
344
+ }
345
+ this._emitPeriodChange();
346
+ }
347
+ /** 기간 변경을 부모로 전파 → 지역 팝업 등 다른 패널이 동일 기간을 쓰도록 동기화 */
348
+ _emitPeriodChange() {
349
+ this.dispatchEvent(new CustomEvent('period-change', {
350
+ detail: { startYearMonth: this.startYearMonth, endYearMonth: this.endYearMonth, isAllPeriod: this.isAllPeriod },
351
+ bubbles: true,
352
+ composed: true
353
+ }));
324
354
  }
325
355
  onCategoryChange(event) {
326
356
  const target = event.target;
@@ -368,7 +398,10 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
368
398
  const condData = [
369
399
  { 항목: 'KPI 카테고리', 값: this.selectedCategory },
370
400
  { 항목: '사업 유형', 값: this.sectorType === 'PUBLIC' ? '공공' : this.sectorType === 'PRIVATE' ? '민간' : '전체' },
371
- { 항목: '건물 용도', 값: this.buildingUsage === 'RESIDENTIAL' ? '주거' : this.buildingUsage === 'NON_RESIDENTIAL' ? '비주거' : '전체' },
401
+ {
402
+ 항목: '건물 용도',
403
+ 값: this.buildingUsage === 'RESIDENTIAL' ? '주거' : this.buildingUsage === 'NON_RESIDENTIAL' ? '비주거' : '전체'
404
+ },
372
405
  { 항목: '기간', 값: this.isAllPeriod ? '전체 기간' : `${this.startYearMonth} ~ ${this.endYearMonth}` },
373
406
  { 항목: '다운로드 일시', 값: new Date().toLocaleString('ko-KR') }
374
407
  ];
@@ -445,7 +478,7 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
445
478
  return '▲';
446
479
  if (change < 0)
447
480
  return '▼';
448
- return '';
481
+ return ''; // 변동 없음(0)이면 화살표 미표시
449
482
  }
450
483
  render() {
451
484
  return html `
@@ -495,33 +528,49 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
495
528
  <!-- 기간 필터 -->
496
529
  <div class="filter-row">
497
530
  <span class="filter-label">기간</span>
498
- <select class="filter-select" ?disabled=${this.isAllPeriod} @change=${(e) => {
531
+ <select
532
+ class="filter-select"
533
+ ?disabled=${this.isAllPeriod}
534
+ @change=${(e) => {
499
535
  const y = e.target.value;
500
536
  const m = this.startYearMonth.split('-')[1] || '01';
501
- this.startYearMonth = `${y}-${m}`;
502
- }}>
537
+ this._setStartYearMonth(`${y}-${m}`);
538
+ }}
539
+ >
503
540
  ${[2022, 2023, 2024, 2025, 2026].map(y => html `<option value="${y}" ?selected=${this.startYearMonth.startsWith(String(y))}>${y}</option>`)}
504
541
  </select>
505
- <select class="filter-select" ?disabled=${this.isAllPeriod} @change=${(e) => {
542
+ <select
543
+ class="filter-select"
544
+ ?disabled=${this.isAllPeriod}
545
+ @change=${(e) => {
506
546
  const y = this.startYearMonth.split('-')[0] || '2025';
507
547
  const m = e.target.value.padStart(2, '0');
508
- this.startYearMonth = `${y}-${m}`;
509
- }}>
548
+ this._setStartYearMonth(`${y}-${m}`);
549
+ }}
550
+ >
510
551
  ${[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map(m => html `<option value="${m}" ?selected=${parseInt(this.startYearMonth.split('-')[1]) === m}>${m}월</option>`)}
511
552
  </select>
512
553
  <span style="color:#6c757d;font-size:0.8rem;">~</span>
513
- <select class="filter-select" ?disabled=${this.isAllPeriod} @change=${(e) => {
554
+ <select
555
+ class="filter-select"
556
+ ?disabled=${this.isAllPeriod}
557
+ @change=${(e) => {
514
558
  const y = e.target.value;
515
559
  const m = this.endYearMonth.split('-')[1] || '12';
516
- this.endYearMonth = `${y}-${m}`;
517
- }}>
560
+ this._setEndYearMonth(`${y}-${m}`);
561
+ }}
562
+ >
518
563
  ${[2022, 2023, 2024, 2025, 2026].map(y => html `<option value="${y}" ?selected=${this.endYearMonth.startsWith(String(y))}>${y}</option>`)}
519
564
  </select>
520
- <select class="filter-select" ?disabled=${this.isAllPeriod} @change=${(e) => {
565
+ <select
566
+ class="filter-select"
567
+ ?disabled=${this.isAllPeriod}
568
+ @change=${(e) => {
521
569
  const y = this.endYearMonth.split('-')[0] || '2026';
522
570
  const m = e.target.value.padStart(2, '0');
523
- this.endYearMonth = `${y}-${m}`;
524
- }}>
571
+ this._setEndYearMonth(`${y}-${m}`);
572
+ }}
573
+ >
525
574
  ${[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map(m => html `<option value="${m}" ?selected=${parseInt(this.endYearMonth.split('-')[1]) === m}>${m}월</option>`)}
526
575
  </select>
527
576
  <label style="display:flex;align-items:center;gap:3px;cursor:pointer;">
@@ -532,7 +581,7 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
532
581
 
533
582
  <!-- 종합 성과 -->
534
583
  <div class="chart-section">
535
- <div class="sub-title">종합 성과</div>
584
+ <div class="sub-title">성과영역별 KPI</div>
536
585
  <div class="chart-toggle">
537
586
  <button
538
587
  class="toggle-button ${this.selectedChartType === 'boxplot' ? 'active' : ''}"
@@ -548,21 +597,32 @@ let KpiLeftPanel = class KpiLeftPanel extends LitElement {
548
597
  </button>
549
598
  </div>
550
599
  <div class="chart-container">
551
- ${this.selectedChartType === 'boxplot'
552
- ? html ` <sv-kpi-boxplot-chart .data=${this.chartData} .valueKey=${'value'}></sv-kpi-boxplot-chart> `
553
- : html `
554
- <sv-kpi-radar-chart
555
- .data=${this.chartData}
556
- .categories=${this.chartCategories}
557
- .valueKey=${'value'}
558
- ></sv-kpi-radar-chart>
559
- `}
600
+ ${this.chartData.length === 0
601
+ ? html `<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#888;font-size:0.9rem;">
602
+ 선택한 기간에 데이터가 없습니다.
603
+ </div>`
604
+ : this.selectedChartType === 'boxplot'
605
+ ? html `
606
+ <sv-kpi-boxplot-chart
607
+ .data=${this.chartData}
608
+ .valueKey=${'value'}
609
+ .valueLabel=${'전국'}
610
+ .showStatus=${false}
611
+ ></sv-kpi-boxplot-chart>
612
+ `
613
+ : html `
614
+ <sv-kpi-radar-chart
615
+ .data=${this.chartData}
616
+ .categories=${this.chartCategories}
617
+ .valueKey=${'value'}
618
+ ></sv-kpi-radar-chart>
619
+ `}
560
620
  </div>
561
621
  </div>
562
622
 
563
623
  <!-- 시도별 성과 -->
564
624
  <div class="chart-section">
565
- <div class="sub-title">시도별 성과</div>
625
+ <div class="sub-title">시도별 종합 KPI</div>
566
626
  <table class="performance-table">
567
627
  <thead>
568
628
  <tr>
@@ -732,7 +792,7 @@ KpiLeftPanel.styles = [
732
792
  color: #dc3545;
733
793
  }
734
794
  .change-down {
735
- color: #198754;
795
+ color: #2196f3;
736
796
  }
737
797
  .change-neutral {
738
798
  color: #6c757d;