@dssp/dkpi 1.0.30 → 1.0.33
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.
- package/dist-client/pages/project-complete-tabs/pc-tab1-plan.d.ts +3 -10
- package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js +63 -79
- package/dist-client/pages/project-complete-tabs/pc-tab1-plan.js.map +1 -1
- package/dist-client/pages/project-complete-tabs/pc-tab2-rating.d.ts +2 -0
- package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js +28 -11
- package/dist-client/pages/project-complete-tabs/pc-tab2-rating.js.map +1 -1
- package/dist-client/pages/project-complete-tabs/pc-tab3-upload.d.ts +2 -0
- package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js +42 -16
- package/dist-client/pages/project-complete-tabs/pc-tab3-upload.js.map +1 -1
- package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.d.ts +2 -1
- package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.js +31 -20
- package/dist-client/pages/project-complete-tabs/pc-tab4-monthly.js.map +1 -1
- package/dist-client/pages/sv-project-detail.js +34 -27
- package/dist-client/pages/sv-project-detail.js.map +1 -1
- package/dist-client/pages/sv-project-update.d.ts +2 -3
- package/dist-client/pages/sv-project-update.js +156 -17
- package/dist-client/pages/sv-project-update.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.d.ts +6 -1
- package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js +12 -15
- package/dist-server/service/kpi-metric-value/kpi-metric-value-mutation.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/schema.graphql +1932 -125
|
@@ -6,6 +6,7 @@ import moment from 'moment-timezone';
|
|
|
6
6
|
import { notify } from '@operato/layout';
|
|
7
7
|
import { hasPrivilege } from '@things-factory/auth-base/dist-client';
|
|
8
8
|
import '@things-factory/kpi/dist-client/viewparts/kpi-metric-input';
|
|
9
|
+
import { ProjectState } from '../sv-project-list';
|
|
9
10
|
let SvProjectCompleteTab2Rating = class SvProjectCompleteTab2Rating extends LitElement {
|
|
10
11
|
constructor() {
|
|
11
12
|
super(...arguments);
|
|
@@ -15,6 +16,11 @@ let SvProjectCompleteTab2Rating = class SvProjectCompleteTab2Rating extends LitE
|
|
|
15
16
|
/** kpi:assessment — Step2 평가 저장 권한 */
|
|
16
17
|
this.canSave = false;
|
|
17
18
|
}
|
|
19
|
+
/** 완료(COMPLETED) 프로젝트 — 입력된 값은 보이지만 입력·저장은 하지 않는다. */
|
|
20
|
+
get isCompleted() {
|
|
21
|
+
var _a;
|
|
22
|
+
return ((_a = this.project) === null || _a === void 0 ? void 0 : _a.state) === ProjectState.COMPLETED;
|
|
23
|
+
}
|
|
18
24
|
render() {
|
|
19
25
|
// 전월 (last month) YYYY-MM. 월별 metric 의 "전월 데이터 입력" 검사 기준.
|
|
20
26
|
const lastMonth = moment().tz('Asia/Seoul').subtract(1, 'month').format('YYYY-MM');
|
|
@@ -57,7 +63,7 @@ let SvProjectCompleteTab2Rating = class SvProjectCompleteTab2Rating extends LitE
|
|
|
57
63
|
<kpi-metric-input
|
|
58
64
|
.metric=${this._withRatingHint(metric)}
|
|
59
65
|
.value=${(_b = kpiMetricValue === null || kpiMetricValue === void 0 ? void 0 : kpiMetricValue.value) !== null && _b !== void 0 ? _b : null}
|
|
60
|
-
?disabled=${!this.canSave}
|
|
66
|
+
?disabled=${!this.canSave || this.isCompleted}
|
|
61
67
|
@metric-input-change=${(e) => this._setRating(metric.id, e.detail.value)}
|
|
62
68
|
></kpi-metric-input>
|
|
63
69
|
</div>
|
|
@@ -65,16 +71,18 @@ let SvProjectCompleteTab2Rating = class SvProjectCompleteTab2Rating extends LitE
|
|
|
65
71
|
`;
|
|
66
72
|
})}
|
|
67
73
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
${this.isCompleted
|
|
75
|
+
? html `<div class="locked-note">🔒 완료된 프로젝트 — 입력된 값만 볼 수 있습니다.</div>`
|
|
76
|
+
: html `<div class="button-line">
|
|
77
|
+
<div class="ghost-btn" @click=${this._reset}>초기화</div>
|
|
78
|
+
<div
|
|
79
|
+
class="ghost-btn secondary ${this.canSave ? '' : 'disabled'}"
|
|
80
|
+
title=${this.canSave ? '' : 'kpi:assessment 권한 필요'}
|
|
81
|
+
@click=${() => this.canSave && this._save()}
|
|
82
|
+
>
|
|
83
|
+
저장
|
|
84
|
+
</div>
|
|
85
|
+
</div>`}
|
|
78
86
|
</div>
|
|
79
87
|
`;
|
|
80
88
|
}
|
|
@@ -176,6 +184,8 @@ let SvProjectCompleteTab2Rating = class SvProjectCompleteTab2Rating extends LitE
|
|
|
176
184
|
}
|
|
177
185
|
async _save() {
|
|
178
186
|
var _a;
|
|
187
|
+
if (this.isCompleted)
|
|
188
|
+
return;
|
|
179
189
|
const response = await updateProjectCompleteStep2(this.kpiMetricValues, (_a = this.project) === null || _a === void 0 ? void 0 : _a.code);
|
|
180
190
|
if (!response.errors) {
|
|
181
191
|
notify({ message: '저장되었습니다.' });
|
|
@@ -319,6 +329,13 @@ SvProjectCompleteTab2Rating.styles = [
|
|
|
319
329
|
opacity: 0.45;
|
|
320
330
|
cursor: not-allowed;
|
|
321
331
|
}
|
|
332
|
+
|
|
333
|
+
.locked-note {
|
|
334
|
+
margin-top: 16px;
|
|
335
|
+
text-align: right;
|
|
336
|
+
font-size: 13px;
|
|
337
|
+
color: #6c757d;
|
|
338
|
+
}
|
|
322
339
|
`
|
|
323
340
|
];
|
|
324
341
|
__decorate([
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pc-tab2-rating.js","sourceRoot":"","sources":["../../../client/pages/project-complete-tabs/pc-tab2-rating.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { getKpiCategories, getKpiMetrics, getKpiMetricValues, updateProjectCompleteStep2 } from '../../shared/complete-api'\nimport moment from 'moment-timezone'\nimport { notify } from '@operato/layout'\nimport { hasPrivilege } from '@things-factory/auth-base/dist-client'\nimport '@things-factory/kpi/dist-client/viewparts/kpi-metric-input'\n\n@customElement('sv-pc-tab2-rating')\nexport class SvProjectCompleteTab2Rating extends LitElement {\n static styles = [\n css`\n :host {\n display: block;\n }\n .title {\n color: #212529;\n font-size: 13px;\n font-weight: 400;\n line-height: 24px;\n text-align: center;\n }\n\n .rows {\n display: flex;\n flex-direction: column;\n padding: 8px 6px;\n }\n .row.header {\n min-height: 35px;\n background: #f3f3fa;\n border-top: 2px #0c4da2 solid;\n grid-template-columns: 300px 1fr 1fr;\n padding: 0px 25px;\n\n .header-label {\n color: #212529;\n text-align: center;\n }\n }\n .row {\n display: grid;\n grid-template-columns: 300px 1fr 1fr;\n gap: 6px 10px;\n padding: 8px 25px;\n align-items: center;\n border-bottom: 1px rgba(0, 0, 0, 0.1) solid;\n\n .cell {\n display: flex;\n align-items: center;\n justify-content: center;\n }\n }\n .label {\n color: #35618e;\n font-size: 16px;\n letter-spacing: -0.05em;\n white-space: nowrap;\n display: flex;\n justify-content: center;\n }\n .stars {\n display: inline-flex;\n gap: 6px;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n }\n .star-wrap {\n position: relative;\n width: 28px;\n height: 28px;\n display: inline-block;\n }\n .star-base,\n .star-fill {\n position: absolute;\n top: 0;\n left: 0;\n font-size: 28px;\n line-height: 28px;\n user-select: none;\n }\n .star-base {\n color: #d0d7e2;\n }\n .star-fill {\n color: #ffb400;\n overflow: hidden;\n width: 0%;\n }\n .star-wrap {\n cursor: pointer;\n }\n .score {\n color: #212529;\n text-align: right;\n }\n .unit {\n text-align: center;\n color: #212529;\n }\n .plus {\n color: #e13232;\n font-weight: 700;\n }\n .minus {\n color: #1e88e5;\n font-weight: 700;\n }\n /* 미입력 — kpiMetricValue 가 비어있는 metric 행 */\n .label.pending::before {\n content: '⚠';\n color: #e74c3c;\n margin-right: 4px;\n }\n .stars.pending .star-base {\n color: #e74c3c;\n }\n\n .button-line {\n display: flex;\n justify-content: center;\n gap: 10px;\n margin-top: 16px;\n }\n .ghost-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 10px;\n background: #35618e;\n color: #ffffff;\n border-radius: 5px;\n cursor: pointer;\n }\n .ghost-btn.secondary {\n background: #24be7b;\n }\n .ghost-btn.disabled {\n opacity: 0.45;\n cursor: not-allowed;\n }\n `\n ]\n\n @property({ type: Object }) project: any = {}\n @state() kpiMetricValues: any = []\n @state() kpiMetrics: any = []\n /** kpi:assessment — Step2 평가 저장 권한 */\n @state() canSave = false\n\n render() {\n // 전월 (last month) YYYY-MM. 월별 metric 의 \"전월 데이터 입력\" 검사 기준.\n const lastMonth = moment().tz('Asia/Seoul').subtract(1, 'month').format('YYYY-MM')\n\n return html`\n <div class=\"title\">\n <div>\n 당초 계획 대비 실제 진행 과정에서 변동된 공사비, 공기(공사기간), 면적, 기타 주요 항목을 현실에 맞게 수정·입력합니다.\n <br />이 정보는 성과 분석, KPI 평가, 통계 산출 등에 기준값으로 사용되므로, 가능한 한 실제 값 기준으로 정확히\n 입력해주시기 바랍니다.\n </div>\n </div>\n\n <div class=\"rows\">\n <div class=\"row header\">\n <div class=\"header-label\">성과영역</div>\n <div class=\"header-label\">현재 평가값</div>\n <div class=\"header-label\">완료 평가</div>\n </div>\n\n ${this.kpiMetrics.map((metric, idx) => {\n const isMonthly = metric.periodType === 'MONTH'\n // MONTH 는 '월별 데이터' 탭의 최신월과 양방향 동기화되도록 그 metric 의 실제 최신월을\n // 읽는다 (데이터 없으면 전월로 폴백). 종료 프로젝트는 최종월이 곧 최신월.\n const targetMonth = isMonthly ? this._latestMonthOf(metric.id) || lastMonth : ''\n const kpiMetricValue = isMonthly\n ? this.kpiMetricValues.find(\n (item: any) =>\n item.metricId === metric.id && item.periodType === 'MONTH' && (item.valueDate || '').startsWith(targetMonth)\n ) || {}\n : this.kpiMetricValues.find((item: any) => item.metricId === metric.id && item.periodType === metric.periodType) || {}\n const isPending = kpiMetricValue.value === undefined || kpiMetricValue.value === null\n\n return html`\n <div class=\"row\">\n <div\n class=\"label ${isPending ? 'pending' : ''}\"\n title=${isPending ? (isMonthly ? '전월 데이터 입력 필요' : '아직 평가되지 않은 항목') : ''}\n >\n • ${metric.name}\n </div>\n <div class=\"cell\">${kpiMetricValue?.value ?? '-'}</div>\n <div class=\"stars ${isPending ? 'pending' : ''}\">\n <kpi-metric-input\n .metric=${this._withRatingHint(metric)}\n .value=${kpiMetricValue?.value ?? null}\n ?disabled=${!this.canSave}\n @metric-input-change=${(e: CustomEvent) => this._setRating(metric.id, e.detail.value)}\n ></kpi-metric-input>\n </div>\n </div>\n `\n })}\n\n <div class=\"button-line\">\n <div class=\"ghost-btn\" @click=${this._reset}>초기화</div>\n <div\n class=\"ghost-btn secondary ${this.canSave ? '' : 'disabled'}\"\n title=${this.canSave ? '' : 'kpi:assessment 권한 필요'}\n @click=${() => this.canSave && this._save()}\n >\n 저장\n </div>\n </div>\n </div>\n `\n }\n\n async connectedCallback() {\n super.connectedCallback()\n this.canSave = await hasPrivilege({\n category: 'kpi',\n privilege: 'assessment',\n domainOwnerGranted: true,\n superUserGranted: true\n })\n if (this.project?.id) {\n this._getInitData()\n }\n }\n\n willUpdate(changedProperties: Map<string, any>) {\n super.willUpdate(changedProperties)\n\n // project가 변경되고, project.id가 존재하면 데이터 로드\n if (changedProperties.has('project') && this.project?.id) {\n this._getInitData()\n }\n }\n\n private async _getInitData() {\n const kpiMetrics = await getKpiMetrics(this.project?.code)\n // 정성 평가 항목만 — 순서는 getKpiMetrics 가 rank 기준으로 이미 정렬.\n this.kpiMetrics = kpiMetrics.filter(item => item.name.includes('정성 평가')) || []\n this.kpiMetricValues = await getKpiMetricValues(this.project.id, this.project?.code)\n }\n\n /** 그 metric 의 월별(MONTH) 데이터 중 가장 최신 월(YYYY-MM). 없으면 null.\n * '완료 평가' 는 이 최신월을 읽고/쓰므로 '월별 데이터' 탭 최신월과 양방향 동기화된다. */\n private _latestMonthOf(metricId: string): string | null {\n const months = (this.kpiMetricValues || [])\n .filter((i: any) => i.metricId === metricId && i.periodType === 'MONTH' && i.valueDate)\n .map((i: any) => (i.valueDate as string).slice(0, 7))\n if (!months.length) return null\n return months.sort((a: string, b: string) => b.localeCompare(a))[0]\n }\n\n /** 완료평가 탭 — 모든 metric 을 1~5 별점으로. DB meta.input 이 있으면 우선. */\n private _withRatingHint(metric: any) {\n if (metric?.meta?.input) return metric\n return { ...metric, meta: { ...(metric?.meta || {}), input: { widget: 'rating', max: 5, step: 1 } } }\n }\n\n private _setRating(metricId: string, score5: number) {\n // score5: 1~5 정수 (별 전체 클릭).\n const metric = this.kpiMetrics.find((m: any) => m.id === metricId)\n if (!metric) return\n\n const today = moment().tz('Asia/Seoul')\n const todayYmd = today.format('YYYY-MM-DD')\n const periodType = metric.periodType\n let updated = [...this.kpiMetricValues]\n\n if (periodType === 'MONTH') {\n // '월별 데이터' 탭의 최신월과 같은 레코드를 수정해야 양방향 동기화가 된다.\n // 최신월이 없으면(데이터 없음) 전월로 폴백해 신규 생성.\n const targetYm = this._latestMonthOf(metricId) || today.clone().subtract(1, 'month').format('YYYY-MM')\n const target1 = `${targetYm}-01`\n const idx = updated.findIndex(\n (i: any) => i.metricId === metricId && i.periodType === 'MONTH' && (i.valueDate || '').startsWith(targetYm)\n )\n if (idx !== -1) {\n updated[idx] = { ...updated[idx], value: score5 }\n } else {\n updated.push({\n id: crypto.randomUUID(),\n value: score5,\n metricId,\n unit: metric.unit || '',\n org: this.project.id,\n periodType: 'MONTH',\n valueDate: target1\n })\n }\n } else {\n // ALLTIME / DAY / 기타 — metric.periodType 그대로 단일 row.\n const idx = updated.findIndex((i: any) => i.metricId === metricId && i.periodType === periodType)\n if (idx !== -1) {\n updated[idx] = { ...updated[idx], value: score5 }\n } else {\n updated.push({\n id: crypto.randomUUID(),\n value: score5,\n metricId,\n unit: metric.unit || '',\n org: this.project.id,\n periodType,\n valueDate: todayYmd\n })\n }\n }\n\n this.kpiMetricValues = updated\n this.dispatchEvent(new CustomEvent('complete-data-change', { detail: { tab: 2, data: updated } }))\n }\n\n private async _save() {\n const response = await updateProjectCompleteStep2(this.kpiMetricValues, this.project?.code)\n if (!response.errors) {\n notify({ message: '저장되었습니다.' })\n }\n }\n\n private _reset() {\n this._getInitData()\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"pc-tab2-rating.js","sourceRoot":"","sources":["../../../client/pages/project-complete-tabs/pc-tab2-rating.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { getKpiCategories, getKpiMetrics, getKpiMetricValues, updateProjectCompleteStep2 } from '../../shared/complete-api'\nimport moment from 'moment-timezone'\nimport { notify } from '@operato/layout'\nimport { hasPrivilege } from '@things-factory/auth-base/dist-client'\nimport '@things-factory/kpi/dist-client/viewparts/kpi-metric-input'\nimport { ProjectState } from '../sv-project-list'\n\n@customElement('sv-pc-tab2-rating')\nexport class SvProjectCompleteTab2Rating extends LitElement {\n static styles = [\n css`\n :host {\n display: block;\n }\n .title {\n color: #212529;\n font-size: 13px;\n font-weight: 400;\n line-height: 24px;\n text-align: center;\n }\n\n .rows {\n display: flex;\n flex-direction: column;\n padding: 8px 6px;\n }\n .row.header {\n min-height: 35px;\n background: #f3f3fa;\n border-top: 2px #0c4da2 solid;\n grid-template-columns: 300px 1fr 1fr;\n padding: 0px 25px;\n\n .header-label {\n color: #212529;\n text-align: center;\n }\n }\n .row {\n display: grid;\n grid-template-columns: 300px 1fr 1fr;\n gap: 6px 10px;\n padding: 8px 25px;\n align-items: center;\n border-bottom: 1px rgba(0, 0, 0, 0.1) solid;\n\n .cell {\n display: flex;\n align-items: center;\n justify-content: center;\n }\n }\n .label {\n color: #35618e;\n font-size: 16px;\n letter-spacing: -0.05em;\n white-space: nowrap;\n display: flex;\n justify-content: center;\n }\n .stars {\n display: inline-flex;\n gap: 6px;\n align-items: center;\n justify-content: center;\n cursor: pointer;\n }\n .star-wrap {\n position: relative;\n width: 28px;\n height: 28px;\n display: inline-block;\n }\n .star-base,\n .star-fill {\n position: absolute;\n top: 0;\n left: 0;\n font-size: 28px;\n line-height: 28px;\n user-select: none;\n }\n .star-base {\n color: #d0d7e2;\n }\n .star-fill {\n color: #ffb400;\n overflow: hidden;\n width: 0%;\n }\n .star-wrap {\n cursor: pointer;\n }\n .score {\n color: #212529;\n text-align: right;\n }\n .unit {\n text-align: center;\n color: #212529;\n }\n .plus {\n color: #e13232;\n font-weight: 700;\n }\n .minus {\n color: #1e88e5;\n font-weight: 700;\n }\n /* 미입력 — kpiMetricValue 가 비어있는 metric 행 */\n .label.pending::before {\n content: '⚠';\n color: #e74c3c;\n margin-right: 4px;\n }\n .stars.pending .star-base {\n color: #e74c3c;\n }\n\n .button-line {\n display: flex;\n justify-content: center;\n gap: 10px;\n margin-top: 16px;\n }\n .ghost-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 10px;\n background: #35618e;\n color: #ffffff;\n border-radius: 5px;\n cursor: pointer;\n }\n .ghost-btn.secondary {\n background: #24be7b;\n }\n .ghost-btn.disabled {\n opacity: 0.45;\n cursor: not-allowed;\n }\n\n .locked-note {\n margin-top: 16px;\n text-align: right;\n font-size: 13px;\n color: #6c757d;\n }\n `\n ]\n\n @property({ type: Object }) project: any = {}\n @state() kpiMetricValues: any = []\n @state() kpiMetrics: any = []\n /** kpi:assessment — Step2 평가 저장 권한 */\n @state() canSave = false\n\n /** 완료(COMPLETED) 프로젝트 — 입력된 값은 보이지만 입력·저장은 하지 않는다. */\n private get isCompleted(): boolean {\n return this.project?.state === ProjectState.COMPLETED\n }\n\n render() {\n // 전월 (last month) YYYY-MM. 월별 metric 의 \"전월 데이터 입력\" 검사 기준.\n const lastMonth = moment().tz('Asia/Seoul').subtract(1, 'month').format('YYYY-MM')\n\n return html`\n <div class=\"title\">\n <div>\n 당초 계획 대비 실제 진행 과정에서 변동된 공사비, 공기(공사기간), 면적, 기타 주요 항목을 현실에 맞게 수정·입력합니다.\n <br />이 정보는 성과 분석, KPI 평가, 통계 산출 등에 기준값으로 사용되므로, 가능한 한 실제 값 기준으로 정확히\n 입력해주시기 바랍니다.\n </div>\n </div>\n\n <div class=\"rows\">\n <div class=\"row header\">\n <div class=\"header-label\">성과영역</div>\n <div class=\"header-label\">현재 평가값</div>\n <div class=\"header-label\">완료 평가</div>\n </div>\n\n ${this.kpiMetrics.map((metric, idx) => {\n const isMonthly = metric.periodType === 'MONTH'\n // MONTH 는 '월별 데이터' 탭의 최신월과 양방향 동기화되도록 그 metric 의 실제 최신월을\n // 읽는다 (데이터 없으면 전월로 폴백). 종료 프로젝트는 최종월이 곧 최신월.\n const targetMonth = isMonthly ? this._latestMonthOf(metric.id) || lastMonth : ''\n const kpiMetricValue = isMonthly\n ? this.kpiMetricValues.find(\n (item: any) =>\n item.metricId === metric.id && item.periodType === 'MONTH' && (item.valueDate || '').startsWith(targetMonth)\n ) || {}\n : this.kpiMetricValues.find((item: any) => item.metricId === metric.id && item.periodType === metric.periodType) || {}\n const isPending = kpiMetricValue.value === undefined || kpiMetricValue.value === null\n\n return html`\n <div class=\"row\">\n <div\n class=\"label ${isPending ? 'pending' : ''}\"\n title=${isPending ? (isMonthly ? '전월 데이터 입력 필요' : '아직 평가되지 않은 항목') : ''}\n >\n • ${metric.name}\n </div>\n <div class=\"cell\">${kpiMetricValue?.value ?? '-'}</div>\n <div class=\"stars ${isPending ? 'pending' : ''}\">\n <kpi-metric-input\n .metric=${this._withRatingHint(metric)}\n .value=${kpiMetricValue?.value ?? null}\n ?disabled=${!this.canSave || this.isCompleted}\n @metric-input-change=${(e: CustomEvent) => this._setRating(metric.id, e.detail.value)}\n ></kpi-metric-input>\n </div>\n </div>\n `\n })}\n\n ${this.isCompleted\n ? html`<div class=\"locked-note\">🔒 완료된 프로젝트 — 입력된 값만 볼 수 있습니다.</div>`\n : html`<div class=\"button-line\">\n <div class=\"ghost-btn\" @click=${this._reset}>초기화</div>\n <div\n class=\"ghost-btn secondary ${this.canSave ? '' : 'disabled'}\"\n title=${this.canSave ? '' : 'kpi:assessment 권한 필요'}\n @click=${() => this.canSave && this._save()}\n >\n 저장\n </div>\n </div>`}\n </div>\n `\n }\n\n async connectedCallback() {\n super.connectedCallback()\n this.canSave = await hasPrivilege({\n category: 'kpi',\n privilege: 'assessment',\n domainOwnerGranted: true,\n superUserGranted: true\n })\n if (this.project?.id) {\n this._getInitData()\n }\n }\n\n willUpdate(changedProperties: Map<string, any>) {\n super.willUpdate(changedProperties)\n\n // project가 변경되고, project.id가 존재하면 데이터 로드\n if (changedProperties.has('project') && this.project?.id) {\n this._getInitData()\n }\n }\n\n private async _getInitData() {\n const kpiMetrics = await getKpiMetrics(this.project?.code)\n // 정성 평가 항목만 — 순서는 getKpiMetrics 가 rank 기준으로 이미 정렬.\n this.kpiMetrics = kpiMetrics.filter(item => item.name.includes('정성 평가')) || []\n this.kpiMetricValues = await getKpiMetricValues(this.project.id, this.project?.code)\n }\n\n /** 그 metric 의 월별(MONTH) 데이터 중 가장 최신 월(YYYY-MM). 없으면 null.\n * '완료 평가' 는 이 최신월을 읽고/쓰므로 '월별 데이터' 탭 최신월과 양방향 동기화된다. */\n private _latestMonthOf(metricId: string): string | null {\n const months = (this.kpiMetricValues || [])\n .filter((i: any) => i.metricId === metricId && i.periodType === 'MONTH' && i.valueDate)\n .map((i: any) => (i.valueDate as string).slice(0, 7))\n if (!months.length) return null\n return months.sort((a: string, b: string) => b.localeCompare(a))[0]\n }\n\n /** 완료평가 탭 — 모든 metric 을 1~5 별점으로. DB meta.input 이 있으면 우선. */\n private _withRatingHint(metric: any) {\n if (metric?.meta?.input) return metric\n return { ...metric, meta: { ...(metric?.meta || {}), input: { widget: 'rating', max: 5, step: 1 } } }\n }\n\n private _setRating(metricId: string, score5: number) {\n // score5: 1~5 정수 (별 전체 클릭).\n const metric = this.kpiMetrics.find((m: any) => m.id === metricId)\n if (!metric) return\n\n const today = moment().tz('Asia/Seoul')\n const todayYmd = today.format('YYYY-MM-DD')\n const periodType = metric.periodType\n let updated = [...this.kpiMetricValues]\n\n if (periodType === 'MONTH') {\n // '월별 데이터' 탭의 최신월과 같은 레코드를 수정해야 양방향 동기화가 된다.\n // 최신월이 없으면(데이터 없음) 전월로 폴백해 신규 생성.\n const targetYm = this._latestMonthOf(metricId) || today.clone().subtract(1, 'month').format('YYYY-MM')\n const target1 = `${targetYm}-01`\n const idx = updated.findIndex(\n (i: any) => i.metricId === metricId && i.periodType === 'MONTH' && (i.valueDate || '').startsWith(targetYm)\n )\n if (idx !== -1) {\n updated[idx] = { ...updated[idx], value: score5 }\n } else {\n updated.push({\n id: crypto.randomUUID(),\n value: score5,\n metricId,\n unit: metric.unit || '',\n org: this.project.id,\n periodType: 'MONTH',\n valueDate: target1\n })\n }\n } else {\n // ALLTIME / DAY / 기타 — metric.periodType 그대로 단일 row.\n const idx = updated.findIndex((i: any) => i.metricId === metricId && i.periodType === periodType)\n if (idx !== -1) {\n updated[idx] = { ...updated[idx], value: score5 }\n } else {\n updated.push({\n id: crypto.randomUUID(),\n value: score5,\n metricId,\n unit: metric.unit || '',\n org: this.project.id,\n periodType,\n valueDate: todayYmd\n })\n }\n }\n\n this.kpiMetricValues = updated\n this.dispatchEvent(new CustomEvent('complete-data-change', { detail: { tab: 2, data: updated } }))\n }\n\n private async _save() {\n if (this.isCompleted) return\n const response = await updateProjectCompleteStep2(this.kpiMetricValues, this.project?.code)\n if (!response.errors) {\n notify({ message: '저장되었습니다.' })\n }\n }\n\n private _reset() {\n this._getInitData()\n }\n}\n"]}
|
|
@@ -7,6 +7,8 @@ export declare class SvProjectCompleteTab3Upload extends LitElement {
|
|
|
7
7
|
slpaData: any[];
|
|
8
8
|
/** kpi:sentiment — Step3 보고서 첨부/저장 권한 */
|
|
9
9
|
canSave: boolean;
|
|
10
|
+
/** 완료(COMPLETED) 프로젝트 — 입력된 값은 보이지만 입력·저장은 하지 않는다. */
|
|
11
|
+
private get isCompleted();
|
|
10
12
|
connectedCallback(): Promise<void>;
|
|
11
13
|
render(): import("lit-html").TemplateResult<1>;
|
|
12
14
|
willUpdate(changedProperties: Map<string, any>): void;
|
|
@@ -4,6 +4,7 @@ import { customElement, property, state } from 'lit/decorators.js';
|
|
|
4
4
|
import { getProject, updateProjectCompleteStep3, getKpiMetrics, getKpiMetricValues } from '../../shared/complete-api';
|
|
5
5
|
import { fetchSentimentGrades, matchSentimentBand, sentimentBandsOf, SENTIMENT_METRICS } from '../../shared/sentiment-index';
|
|
6
6
|
import { notify } from '@operato/layout';
|
|
7
|
+
import { ProjectState } from '../sv-project-list';
|
|
7
8
|
import { hasPrivilege } from '@things-factory/auth-base/dist-client';
|
|
8
9
|
let SvProjectCompleteTab3Upload = class SvProjectCompleteTab3Upload extends LitElement {
|
|
9
10
|
constructor() {
|
|
@@ -15,6 +16,11 @@ let SvProjectCompleteTab3Upload = class SvProjectCompleteTab3Upload extends LitE
|
|
|
15
16
|
/** kpi:sentiment — Step3 보고서 첨부/저장 권한 */
|
|
16
17
|
this.canSave = false;
|
|
17
18
|
}
|
|
19
|
+
/** 완료(COMPLETED) 프로젝트 — 입력된 값은 보이지만 입력·저장은 하지 않는다. */
|
|
20
|
+
get isCompleted() {
|
|
21
|
+
var _a;
|
|
22
|
+
return ((_a = this.project) === null || _a === void 0 ? void 0 : _a.state) === ProjectState.COMPLETED;
|
|
23
|
+
}
|
|
18
24
|
async connectedCallback() {
|
|
19
25
|
super.connectedCallback();
|
|
20
26
|
this.canSave = await hasPrivilege({
|
|
@@ -43,15 +49,17 @@ let SvProjectCompleteTab3Upload = class SvProjectCompleteTab3Upload extends LitE
|
|
|
43
49
|
<div class="attachment-row">
|
|
44
50
|
<md-icon class="file-icon">picture_as_pdf</md-icon>
|
|
45
51
|
<div class="attachment-name" title="${(_a = this._getCurrentFile()) === null || _a === void 0 ? void 0 : _a.name}">${(_b = this._getCurrentFile()) === null || _b === void 0 ? void 0 : _b.name}</div>
|
|
46
|
-
|
|
52
|
+
${this.isCompleted ? '' : html `<md-icon class="delete-icon" @click=${this._removeFile}>delete</md-icon>`}
|
|
47
53
|
</div>
|
|
48
54
|
</div>
|
|
49
55
|
`
|
|
50
|
-
:
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
: this.isCompleted
|
|
57
|
+
? html `<div class="empty-msg">업로드된 의견서가 없습니다.</div>`
|
|
58
|
+
: html `
|
|
59
|
+
<div class="upload-controls">
|
|
60
|
+
<ox-input-file accept="application/pdf,.pdf" hide-filelist @change=${this._onFileSelect}> </ox-input-file>
|
|
61
|
+
</div>
|
|
62
|
+
`}
|
|
55
63
|
${this.slpaData.length > 0
|
|
56
64
|
? html `
|
|
57
65
|
<div class="slpa-results">
|
|
@@ -88,16 +96,18 @@ let SvProjectCompleteTab3Upload = class SvProjectCompleteTab3Upload extends LitE
|
|
|
88
96
|
`
|
|
89
97
|
: ''}
|
|
90
98
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
${this.isCompleted
|
|
100
|
+
? html `<div class="locked-note">🔒 완료된 프로젝트 — 입력된 값만 볼 수 있습니다.</div>`
|
|
101
|
+
: html `<div class="button-line">
|
|
102
|
+
<div class="ghost-btn " @click=${this._reset}>초기화</div>
|
|
103
|
+
<div
|
|
104
|
+
class="ghost-btn secondary ${this.canSave ? '' : 'disabled'}"
|
|
105
|
+
title=${this.canSave ? '' : 'kpi:sentiment 권한 필요'}
|
|
106
|
+
@click=${() => this.canSave && this._save()}
|
|
107
|
+
>
|
|
108
|
+
저장
|
|
109
|
+
</div>
|
|
110
|
+
</div>`}
|
|
101
111
|
</div>
|
|
102
112
|
`;
|
|
103
113
|
}
|
|
@@ -115,6 +125,8 @@ let SvProjectCompleteTab3Upload = class SvProjectCompleteTab3Upload extends LitE
|
|
|
115
125
|
}
|
|
116
126
|
async _save() {
|
|
117
127
|
var _a;
|
|
128
|
+
if (this.isCompleted)
|
|
129
|
+
return;
|
|
118
130
|
// 대기 중인 파일이 있으면 업로드
|
|
119
131
|
if (this.pendingFile) {
|
|
120
132
|
await this._uploadFile(this.pendingFile);
|
|
@@ -378,6 +390,20 @@ SvProjectCompleteTab3Upload.styles = [
|
|
|
378
390
|
color: #1976d2;
|
|
379
391
|
font-weight: 700;
|
|
380
392
|
}
|
|
393
|
+
|
|
394
|
+
.locked-note {
|
|
395
|
+
margin-top: 16px;
|
|
396
|
+
text-align: right;
|
|
397
|
+
font-size: 13px;
|
|
398
|
+
color: #6c757d;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
.empty-msg {
|
|
402
|
+
text-align: center;
|
|
403
|
+
color: #999;
|
|
404
|
+
padding: 40px 0;
|
|
405
|
+
font-size: 14px;
|
|
406
|
+
}
|
|
381
407
|
`
|
|
382
408
|
];
|
|
383
409
|
__decorate([
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pc-tab3-upload.js","sourceRoot":"","sources":["../../../client/pages/project-complete-tabs/pc-tab3-upload.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { client } from '@operato/graphql'\nimport { gql } from '@apollo/client'\nimport { getProject, updateProjectCompleteStep3, getKpiMetrics, getKpiMetricValues } from '../../shared/complete-api'\nimport {\n fetchSentimentGrades,\n matchSentimentBand,\n sentimentBandsOf,\n SENTIMENT_METRICS\n} from '../../shared/sentiment-index'\nimport { notify } from '@operato/layout'\nimport { hasPrivilege } from '@things-factory/auth-base/dist-client'\n\n@customElement('sv-pc-tab3-upload')\nexport class SvProjectCompleteTab3Upload extends LitElement {\n static styles = [\n css`\n :host {\n display: block;\n }\n .title {\n color: #212529;\n font-size: 13px;\n font-weight: 400;\n line-height: 24px;\n text-align: center;\n }\n\n .rows {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 8px 6px;\n }\n\n .upload-controls {\n display: flex;\n gap: 8px;\n justify-content: center;\n margin-bottom: 16px;\n\n ox-input-file {\n flex: 1;\n max-width: 500px;\n height: 210px;\n }\n }\n\n .attachment-list {\n display: flex;\n justify-content: center;\n margin-top: 8px;\n }\n\n .attachment-row {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 16px;\n padding: 24px 20px;\n border: 2px solid #e3f2fd;\n border-radius: 12px;\n background: linear-gradient(135deg, #f8fbff 0%, #e3f2fd 100%);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n min-width: 300px;\n max-width: 400px;\n }\n\n .file-icon {\n font-size: 40px;\n color: #d32f2f;\n line-height: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n width: auto;\n height: 48px;\n }\n\n .attachment-name {\n font-size: 16px;\n font-weight: 500;\n color: #1976d2;\n text-align: center;\n word-break: break-word;\n line-height: 1.4;\n }\n\n .delete-icon {\n cursor: pointer;\n color: #757575;\n background: rgba(117, 117, 117, 0.1);\n border-radius: 50%;\n padding: 8px;\n transition: all 0.2s ease;\n }\n\n .delete-icon:hover {\n color: #e57373;\n background: rgba(229, 115, 115, 0.15);\n transform: scale(1.1);\n }\n\n .button-line {\n display: flex;\n justify-content: center;\n gap: 10px;\n margin-top: 16px;\n }\n .ghost-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 10px;\n background: #35618e;\n color: #ffffff;\n border-radius: 5px;\n cursor: pointer;\n }\n .ghost-btn.secondary {\n background: #24be7b;\n }\n .ghost-btn.disabled {\n opacity: 0.45;\n cursor: not-allowed;\n }\n\n .slpa-results {\n margin-top: 20px;\n padding: 16px;\n border: 1px solid #e0e0e0;\n border-radius: 8px;\n background: #f8f9fa;\n }\n\n .slpa-title {\n font-size: 16px;\n font-weight: 600;\n color: #35618e;\n margin-bottom: 12px;\n }\n\n .slpa-item {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 8px 0;\n border-bottom: 1px solid #e0e0e0;\n }\n\n .slpa-item:last-child {\n border-bottom: none;\n }\n\n .slpa-label {\n font-weight: 500;\n color: #333;\n }\n\n .slpa-readout {\n display: flex;\n align-items: baseline;\n gap: 10px;\n }\n\n .slpa-value {\n font-weight: 600;\n color: #1976d2;\n }\n\n .slpa-result {\n font-weight: 700;\n color: #35618e;\n }\n\n /* 지수 → 5단계 환산 기준. 회의 자료의 \"지수 범위 / 결과\" 표를 그대로 옮긴 것. */\n .slpa-legend {\n margin: 6px 0 10px auto;\n border-collapse: collapse;\n font-size: 12px;\n color: #555;\n }\n\n .slpa-legend th,\n .slpa-legend td {\n padding: 3px 12px;\n border: 1px solid #e0e0e0;\n text-align: center;\n font-weight: 400;\n }\n\n .slpa-legend th {\n background: #eef2f7;\n color: #35618e;\n font-weight: 600;\n }\n\n .slpa-legend tr.current td {\n background: #e3f2fd;\n color: #1976d2;\n font-weight: 700;\n }\n `\n ]\n\n @property({ type: Object }) project: any = {}\n @state() attachment: any = {}\n @state() private pendingFile: File | null = null\n @state() slpaData: any[] = []\n /** kpi:sentiment — Step3 보고서 첨부/저장 권한 */\n @state() canSave = false\n\n async connectedCallback() {\n super.connectedCallback()\n this.canSave = await hasPrivilege({\n category: 'kpi',\n privilege: 'sentiment',\n domainOwnerGranted: true,\n superUserGranted: true\n })\n }\n\n render() {\n return html`\n <div class=\"title\">\n <div>\n 감리 최종 감리보고서의 종합의견서(PDF 형식)를 시스템에 업로드해 주세요.\n <br />\n 보고서에 포함된 텍스트와 평가 내용을 기반으로 기계 판독 및 AI 기반 프로젝트 평가가 자동으로 진행되므로, 반드시 최종\n 확정된 원본 파일을 제출해 주시기 바랍니다.\n </div>\n </div>\n\n <div class=\"rows\">\n ${this._getCurrentFile()\n ? html`\n <div class=\"attachment-list\">\n <div class=\"attachment-row\">\n <md-icon class=\"file-icon\">picture_as_pdf</md-icon>\n <div class=\"attachment-name\" title=\"${this._getCurrentFile()?.name}\">${this._getCurrentFile()?.name}</div>\n <md-icon class=\"delete-icon\" @click=${this._removeFile}>delete</md-icon>\n </div>\n </div>\n `\n : html`\n <div class=\"upload-controls\">\n <ox-input-file accept=\"application/pdf,.pdf\" hide-filelist @change=${this._onFileSelect}> </ox-input-file>\n </div>\n `}\n ${this.slpaData.length > 0\n ? html`\n <div class=\"slpa-results\">\n <div class=\"slpa-title\">AI 분석 결과 (SL-PA 관련 항목)</div>\n ${this.slpaData.map(\n item => html`\n <div class=\"slpa-item\">\n <div class=\"slpa-label\">${item.label}</div>\n <div class=\"slpa-readout\">\n <span class=\"slpa-value\">${item.value.toFixed(2)} ${item.unit || ''}</span>\n ${item.matched ? html`<span class=\"slpa-result\">${item.matched.result}</span>` : ''}\n </div>\n </div>\n ${item.bands.length > 0\n ? html`\n <table class=\"slpa-legend\">\n <tr>\n <th>지수 범위</th>\n <th>결과</th>\n </tr>\n ${item.bands.map(\n (band: any) => html`\n <tr class=${item.matched?.score === band.score ? 'current' : ''}>\n <td>${band.range}</td>\n <td>${band.result}</td>\n </tr>\n `\n )}\n </table>\n `\n : ''}\n `\n )}\n </div>\n `\n : ''}\n\n <div class=\"button-line\">\n <div class=\"ghost-btn \" @click=${this._reset}>초기화</div>\n <div\n class=\"ghost-btn secondary ${this.canSave ? '' : 'disabled'}\"\n title=${this.canSave ? '' : 'kpi:sentiment 권한 필요'}\n @click=${() => this.canSave && this._save()}\n >\n 저장\n </div>\n </div>\n </div>\n `\n }\n\n willUpdate(changedProperties: Map<string, any>) {\n super.willUpdate(changedProperties)\n\n // project가 변경되고, project.id가 존재하면 데이터 로드\n if (changedProperties.has('project') && this.project?.id) {\n this._getProjectData()\n }\n }\n\n private _getCurrentFile() {\n return this.pendingFile || (this.attachment?.id ? this.attachment : null)\n }\n\n private async _save() {\n // 대기 중인 파일이 있으면 업로드\n if (this.pendingFile) {\n await this._uploadFile(this.pendingFile)\n this.pendingFile = null\n } else if (!this.pendingFile && !this.attachment?.id) {\n notify({ message: '파일은 필수입니다.' })\n }\n }\n\n private _removeFile() {\n this.pendingFile = null\n this.attachment = {}\n this.slpaData = [] // SL-PA 데이터도 초기화\n }\n\n private async _onFileSelect(e: CustomEvent) {\n const files = e.detail as File[]\n if (files.length > 0) {\n this.pendingFile = files[0]\n }\n }\n\n private async _uploadFile(file: File | null) {\n const response = await updateProjectCompleteStep3(file, this.project.id, this.project?.code)\n if (!response.errors) {\n const uploaded = response.data.updateKpiMetricValuesSentiment\n this.attachment = uploaded\n notify({ message: '저장되었습니다.' })\n\n // PDF 업로드 후 SL-PA 관련 데이터를 가져와서 표시\n await this._loadSlpaData()\n }\n }\n\n private async _loadSlpaData() {\n // KPI 메트릭들 가져오기\n const kpiMetrics = await getKpiMetrics(this.project?.code)\n\n // \"SL-PA\"가 포함된 메트릭들만 필터링\n const slpaMetrics = kpiMetrics.filter(metric => metric.name.includes('SL-PA'))\n\n if (slpaMetrics.length > 0) {\n // 해당 프로젝트의 KPI 값들과, 지수 → 5단계 환산에 쓸 구간표를 함께 가져온다\n const [kpiMetricValues, grades] = await Promise.all([\n getKpiMetricValues(this.project.id, this.project?.code),\n fetchSentimentGrades(this.project?.code)\n ])\n\n // SL-PA 메트릭들과 해당 값들을 매칭하여 표시용 데이터 생성\n this.slpaData = slpaMetrics.map(metric => {\n const metricValue = kpiMetricValues.find((item: any) => item.metricId === metric.id)\n const value = metricValue?.value || 0\n\n // 체감지수로 정의된 metric 만 화면 이름과 등급을 갖는다. 그 밖의 SL-PA 항목은\n // 환산 기준이 없으므로 이름과 값 그대로 (기존 동작).\n const sentiment = SENTIMENT_METRICS[metric.name]\n const metricGrades = sentiment ? grades[sentiment.kpiCode] : undefined\n\n return {\n label: sentiment?.label || metric.name,\n value,\n unit: metricValue?.unit || metric.unit || '',\n matched: matchSentimentBand(value, metricGrades),\n bands: sentimentBandsOf(metricGrades)\n }\n })\n }\n }\n\n private _reset() {\n this.pendingFile = null\n this.slpaData = [] // SL-PA 데이터도 초기화\n this._getProjectData()\n }\n\n private async _getProjectData() {\n const project = await getProject(this.project.id)\n this.attachment = project.completeReport\n\n // 기존에 PDF가 업로드되어 있다면 SL-PA 데이터도 로드\n if (this.attachment?.id) {\n await this._loadSlpaData()\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"pc-tab3-upload.js","sourceRoot":"","sources":["../../../client/pages/project-complete-tabs/pc-tab3-upload.ts"],"names":[],"mappings":"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","sourcesContent":["import { css, html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { client } from '@operato/graphql'\nimport { gql } from '@apollo/client'\nimport { getProject, updateProjectCompleteStep3, getKpiMetrics, getKpiMetricValues } from '../../shared/complete-api'\nimport {\n fetchSentimentGrades,\n matchSentimentBand,\n sentimentBandsOf,\n SENTIMENT_METRICS\n} from '../../shared/sentiment-index'\nimport { notify } from '@operato/layout'\nimport { ProjectState } from '../sv-project-list'\nimport { hasPrivilege } from '@things-factory/auth-base/dist-client'\n\n@customElement('sv-pc-tab3-upload')\nexport class SvProjectCompleteTab3Upload extends LitElement {\n static styles = [\n css`\n :host {\n display: block;\n }\n .title {\n color: #212529;\n font-size: 13px;\n font-weight: 400;\n line-height: 24px;\n text-align: center;\n }\n\n .rows {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 8px 6px;\n }\n\n .upload-controls {\n display: flex;\n gap: 8px;\n justify-content: center;\n margin-bottom: 16px;\n\n ox-input-file {\n flex: 1;\n max-width: 500px;\n height: 210px;\n }\n }\n\n .attachment-list {\n display: flex;\n justify-content: center;\n margin-top: 8px;\n }\n\n .attachment-row {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 16px;\n padding: 24px 20px;\n border: 2px solid #e3f2fd;\n border-radius: 12px;\n background: linear-gradient(135deg, #f8fbff 0%, #e3f2fd 100%);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);\n min-width: 300px;\n max-width: 400px;\n }\n\n .file-icon {\n font-size: 40px;\n color: #d32f2f;\n line-height: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n width: auto;\n height: 48px;\n }\n\n .attachment-name {\n font-size: 16px;\n font-weight: 500;\n color: #1976d2;\n text-align: center;\n word-break: break-word;\n line-height: 1.4;\n }\n\n .delete-icon {\n cursor: pointer;\n color: #757575;\n background: rgba(117, 117, 117, 0.1);\n border-radius: 50%;\n padding: 8px;\n transition: all 0.2s ease;\n }\n\n .delete-icon:hover {\n color: #e57373;\n background: rgba(229, 115, 115, 0.15);\n transform: scale(1.1);\n }\n\n .button-line {\n display: flex;\n justify-content: center;\n gap: 10px;\n margin-top: 16px;\n }\n .ghost-btn {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 10px;\n background: #35618e;\n color: #ffffff;\n border-radius: 5px;\n cursor: pointer;\n }\n .ghost-btn.secondary {\n background: #24be7b;\n }\n .ghost-btn.disabled {\n opacity: 0.45;\n cursor: not-allowed;\n }\n\n .slpa-results {\n margin-top: 20px;\n padding: 16px;\n border: 1px solid #e0e0e0;\n border-radius: 8px;\n background: #f8f9fa;\n }\n\n .slpa-title {\n font-size: 16px;\n font-weight: 600;\n color: #35618e;\n margin-bottom: 12px;\n }\n\n .slpa-item {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 8px 0;\n border-bottom: 1px solid #e0e0e0;\n }\n\n .slpa-item:last-child {\n border-bottom: none;\n }\n\n .slpa-label {\n font-weight: 500;\n color: #333;\n }\n\n .slpa-readout {\n display: flex;\n align-items: baseline;\n gap: 10px;\n }\n\n .slpa-value {\n font-weight: 600;\n color: #1976d2;\n }\n\n .slpa-result {\n font-weight: 700;\n color: #35618e;\n }\n\n /* 지수 → 5단계 환산 기준. 회의 자료의 \"지수 범위 / 결과\" 표를 그대로 옮긴 것. */\n .slpa-legend {\n margin: 6px 0 10px auto;\n border-collapse: collapse;\n font-size: 12px;\n color: #555;\n }\n\n .slpa-legend th,\n .slpa-legend td {\n padding: 3px 12px;\n border: 1px solid #e0e0e0;\n text-align: center;\n font-weight: 400;\n }\n\n .slpa-legend th {\n background: #eef2f7;\n color: #35618e;\n font-weight: 600;\n }\n\n .slpa-legend tr.current td {\n background: #e3f2fd;\n color: #1976d2;\n font-weight: 700;\n }\n\n .locked-note {\n margin-top: 16px;\n text-align: right;\n font-size: 13px;\n color: #6c757d;\n }\n\n .empty-msg {\n text-align: center;\n color: #999;\n padding: 40px 0;\n font-size: 14px;\n }\n `\n ]\n\n @property({ type: Object }) project: any = {}\n @state() attachment: any = {}\n @state() private pendingFile: File | null = null\n @state() slpaData: any[] = []\n /** kpi:sentiment — Step3 보고서 첨부/저장 권한 */\n @state() canSave = false\n\n /** 완료(COMPLETED) 프로젝트 — 입력된 값은 보이지만 입력·저장은 하지 않는다. */\n private get isCompleted(): boolean {\n return this.project?.state === ProjectState.COMPLETED\n }\n\n async connectedCallback() {\n super.connectedCallback()\n this.canSave = await hasPrivilege({\n category: 'kpi',\n privilege: 'sentiment',\n domainOwnerGranted: true,\n superUserGranted: true\n })\n }\n\n render() {\n return html`\n <div class=\"title\">\n <div>\n 감리 최종 감리보고서의 종합의견서(PDF 형식)를 시스템에 업로드해 주세요.\n <br />\n 보고서에 포함된 텍스트와 평가 내용을 기반으로 기계 판독 및 AI 기반 프로젝트 평가가 자동으로 진행되므로, 반드시 최종\n 확정된 원본 파일을 제출해 주시기 바랍니다.\n </div>\n </div>\n\n <div class=\"rows\">\n ${this._getCurrentFile()\n ? html`\n <div class=\"attachment-list\">\n <div class=\"attachment-row\">\n <md-icon class=\"file-icon\">picture_as_pdf</md-icon>\n <div class=\"attachment-name\" title=\"${this._getCurrentFile()?.name}\">${this._getCurrentFile()?.name}</div>\n ${this.isCompleted ? '' : html`<md-icon class=\"delete-icon\" @click=${this._removeFile}>delete</md-icon>`}\n </div>\n </div>\n `\n : this.isCompleted\n ? html`<div class=\"empty-msg\">업로드된 의견서가 없습니다.</div>`\n : html`\n <div class=\"upload-controls\">\n <ox-input-file accept=\"application/pdf,.pdf\" hide-filelist @change=${this._onFileSelect}> </ox-input-file>\n </div>\n `}\n ${this.slpaData.length > 0\n ? html`\n <div class=\"slpa-results\">\n <div class=\"slpa-title\">AI 분석 결과 (SL-PA 관련 항목)</div>\n ${this.slpaData.map(\n item => html`\n <div class=\"slpa-item\">\n <div class=\"slpa-label\">${item.label}</div>\n <div class=\"slpa-readout\">\n <span class=\"slpa-value\">${item.value.toFixed(2)} ${item.unit || ''}</span>\n ${item.matched ? html`<span class=\"slpa-result\">${item.matched.result}</span>` : ''}\n </div>\n </div>\n ${item.bands.length > 0\n ? html`\n <table class=\"slpa-legend\">\n <tr>\n <th>지수 범위</th>\n <th>결과</th>\n </tr>\n ${item.bands.map(\n (band: any) => html`\n <tr class=${item.matched?.score === band.score ? 'current' : ''}>\n <td>${band.range}</td>\n <td>${band.result}</td>\n </tr>\n `\n )}\n </table>\n `\n : ''}\n `\n )}\n </div>\n `\n : ''}\n\n ${this.isCompleted\n ? html`<div class=\"locked-note\">🔒 완료된 프로젝트 — 입력된 값만 볼 수 있습니다.</div>`\n : html`<div class=\"button-line\">\n <div class=\"ghost-btn \" @click=${this._reset}>초기화</div>\n <div\n class=\"ghost-btn secondary ${this.canSave ? '' : 'disabled'}\"\n title=${this.canSave ? '' : 'kpi:sentiment 권한 필요'}\n @click=${() => this.canSave && this._save()}\n >\n 저장\n </div>\n </div>`}\n </div>\n `\n }\n\n willUpdate(changedProperties: Map<string, any>) {\n super.willUpdate(changedProperties)\n\n // project가 변경되고, project.id가 존재하면 데이터 로드\n if (changedProperties.has('project') && this.project?.id) {\n this._getProjectData()\n }\n }\n\n private _getCurrentFile() {\n return this.pendingFile || (this.attachment?.id ? this.attachment : null)\n }\n\n private async _save() {\n if (this.isCompleted) return\n // 대기 중인 파일이 있으면 업로드\n if (this.pendingFile) {\n await this._uploadFile(this.pendingFile)\n this.pendingFile = null\n } else if (!this.pendingFile && !this.attachment?.id) {\n notify({ message: '파일은 필수입니다.' })\n }\n }\n\n private _removeFile() {\n this.pendingFile = null\n this.attachment = {}\n this.slpaData = [] // SL-PA 데이터도 초기화\n }\n\n private async _onFileSelect(e: CustomEvent) {\n const files = e.detail as File[]\n if (files.length > 0) {\n this.pendingFile = files[0]\n }\n }\n\n private async _uploadFile(file: File | null) {\n const response = await updateProjectCompleteStep3(file, this.project.id, this.project?.code)\n if (!response.errors) {\n const uploaded = response.data.updateKpiMetricValuesSentiment\n this.attachment = uploaded\n notify({ message: '저장되었습니다.' })\n\n // PDF 업로드 후 SL-PA 관련 데이터를 가져와서 표시\n await this._loadSlpaData()\n }\n }\n\n private async _loadSlpaData() {\n // KPI 메트릭들 가져오기\n const kpiMetrics = await getKpiMetrics(this.project?.code)\n\n // \"SL-PA\"가 포함된 메트릭들만 필터링\n const slpaMetrics = kpiMetrics.filter(metric => metric.name.includes('SL-PA'))\n\n if (slpaMetrics.length > 0) {\n // 해당 프로젝트의 KPI 값들과, 지수 → 5단계 환산에 쓸 구간표를 함께 가져온다\n const [kpiMetricValues, grades] = await Promise.all([\n getKpiMetricValues(this.project.id, this.project?.code),\n fetchSentimentGrades(this.project?.code)\n ])\n\n // SL-PA 메트릭들과 해당 값들을 매칭하여 표시용 데이터 생성\n this.slpaData = slpaMetrics.map(metric => {\n const metricValue = kpiMetricValues.find((item: any) => item.metricId === metric.id)\n const value = metricValue?.value || 0\n\n // 체감지수로 정의된 metric 만 화면 이름과 등급을 갖는다. 그 밖의 SL-PA 항목은\n // 환산 기준이 없으므로 이름과 값 그대로 (기존 동작).\n const sentiment = SENTIMENT_METRICS[metric.name]\n const metricGrades = sentiment ? grades[sentiment.kpiCode] : undefined\n\n return {\n label: sentiment?.label || metric.name,\n value,\n unit: metricValue?.unit || metric.unit || '',\n matched: matchSentimentBand(value, metricGrades),\n bands: sentimentBandsOf(metricGrades)\n }\n })\n }\n }\n\n private _reset() {\n this.pendingFile = null\n this.slpaData = [] // SL-PA 데이터도 초기화\n this._getProjectData()\n }\n\n private async _getProjectData() {\n const project = await getProject(this.project.id)\n this.attachment = project.completeReport\n\n // 기존에 PDF가 업로드되어 있다면 SL-PA 데이터도 로드\n if (this.attachment?.id) {\n await this._loadSlpaData()\n }\n }\n}\n"]}
|
|
@@ -31,8 +31,9 @@ export declare class SvProjectCompleteTab4Monthly extends LitElement {
|
|
|
31
31
|
private _isCellPending;
|
|
32
32
|
/** metric 컬럼이 전 row 통틀어 한 번도 값이 없으면 헤더 강조. */
|
|
33
33
|
private _isMetricNeverEntered;
|
|
34
|
-
/**
|
|
34
|
+
/** 완료(COMPLETED) 프로젝트 — 입력된 값은 보이지만 추가·수정·삭제는 하지 않는다. */
|
|
35
35
|
private get isCompleted();
|
|
36
|
+
private get editable();
|
|
36
37
|
private _addMonth;
|
|
37
38
|
private _removeMonth;
|
|
38
39
|
/**
|
|
@@ -60,7 +60,7 @@ let SvProjectCompleteTab4Monthly = class SvProjectCompleteTab4Monthly extends Li
|
|
|
60
60
|
|
|
61
61
|
${this.isCompleted
|
|
62
62
|
? html `<div class="toolbar">
|
|
63
|
-
<span class="locked-note">🔒
|
|
63
|
+
<span class="locked-note">🔒 완료된 프로젝트 — 입력된 값만 볼 수 있습니다.</span>
|
|
64
64
|
</div>`
|
|
65
65
|
: html `<div class="toolbar">
|
|
66
66
|
<select
|
|
@@ -83,7 +83,9 @@ let SvProjectCompleteTab4Monthly = class SvProjectCompleteTab4Monthly extends Li
|
|
|
83
83
|
월별 metric (periodType=MONTH) 이 등록되어 있지 않습니다. KPI 관리자에서 먼저 등록해주세요.
|
|
84
84
|
</div>`
|
|
85
85
|
: this.monthRows.length === 0
|
|
86
|
-
? html `<div class="empty-msg"
|
|
86
|
+
? html `<div class="empty-msg">
|
|
87
|
+
${this.isCompleted ? '입력된 월별 데이터가 없습니다.' : '등록된 월별 데이터가 없습니다. 위에서 월을 추가해주세요.'}
|
|
88
|
+
</div>`
|
|
87
89
|
: html `
|
|
88
90
|
<div class="grid-wrapper">
|
|
89
91
|
<table>
|
|
@@ -115,7 +117,7 @@ let SvProjectCompleteTab4Monthly = class SvProjectCompleteTab4Monthly extends Li
|
|
|
115
117
|
<kpi-metric-input
|
|
116
118
|
.metric=${this._withInputHint(m)}
|
|
117
119
|
.value=${(_a = row.values[m.id]) !== null && _a !== void 0 ? _a : null}
|
|
118
|
-
?disabled=${!this.
|
|
120
|
+
?disabled=${!this.editable}
|
|
119
121
|
@metric-input-change=${(e) => this._setCell(rowIdx, m.id, e.detail.value)}
|
|
120
122
|
></kpi-metric-input>
|
|
121
123
|
</td>
|
|
@@ -131,7 +133,9 @@ let SvProjectCompleteTab4Monthly = class SvProjectCompleteTab4Monthly extends Li
|
|
|
131
133
|
: html `<span class="status-saved">저장됨</span>`}
|
|
132
134
|
</td>
|
|
133
135
|
<td>
|
|
134
|
-
|
|
136
|
+
${this.isCompleted
|
|
137
|
+
? ''
|
|
138
|
+
: html `<span class="delete-btn" title="삭제" @click=${() => this._removeMonth(rowIdx)}>✕</span>`}
|
|
135
139
|
</td>
|
|
136
140
|
</tr>
|
|
137
141
|
`)}
|
|
@@ -140,16 +144,18 @@ let SvProjectCompleteTab4Monthly = class SvProjectCompleteTab4Monthly extends Li
|
|
|
140
144
|
</div>
|
|
141
145
|
`}
|
|
142
146
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
147
|
+
${this.isCompleted
|
|
148
|
+
? ''
|
|
149
|
+
: html `<div class="button-line">
|
|
150
|
+
<div class="ghost-btn" @click=${this._reset}>초기화</div>
|
|
151
|
+
<div
|
|
152
|
+
class="ghost-btn secondary ${this.canSave ? '' : 'disabled'}"
|
|
153
|
+
title=${this.canSave ? '' : 'kpi:input 권한 필요'}
|
|
154
|
+
@click=${() => this.canSave && this._save()}
|
|
155
|
+
>
|
|
156
|
+
저장
|
|
157
|
+
</div>
|
|
158
|
+
</div>`}
|
|
153
159
|
`;
|
|
154
160
|
}
|
|
155
161
|
willUpdate(changedProperties) {
|
|
@@ -257,7 +263,8 @@ let SvProjectCompleteTab4Monthly = class SvProjectCompleteTab4Monthly extends Li
|
|
|
257
263
|
}
|
|
258
264
|
// project.startDate ~ 현재월 사이 누락된 월 placeholder row 자동 추가.
|
|
259
265
|
// 사용자가 값을 입력하지 않으면 dirty 가 안 되므로 저장 시 무시되어 spurious patch 없음.
|
|
260
|
-
|
|
266
|
+
// 완료된 프로젝트는 입력할 수 없으므로 빈 월을 만들지 않는다 — 입력된 월만 보인다.
|
|
267
|
+
for (const ym of this.isCompleted ? [] : this._generateExpectedMonths()) {
|
|
261
268
|
if (!byMonth.has(ym)) {
|
|
262
269
|
byMonth.set(ym, {
|
|
263
270
|
workDate: ym,
|
|
@@ -313,17 +320,17 @@ let SvProjectCompleteTab4Monthly = class SvProjectCompleteTab4Monthly extends Li
|
|
|
313
320
|
return v !== undefined && v !== null && v !== '';
|
|
314
321
|
});
|
|
315
322
|
}
|
|
316
|
-
/**
|
|
323
|
+
/** 완료(COMPLETED) 프로젝트 — 입력된 값은 보이지만 추가·수정·삭제는 하지 않는다. */
|
|
317
324
|
get isCompleted() {
|
|
318
325
|
var _a;
|
|
319
326
|
return ((_a = this.project) === null || _a === void 0 ? void 0 : _a.state) === ProjectState.COMPLETED;
|
|
320
327
|
}
|
|
328
|
+
get editable() {
|
|
329
|
+
return this.canSave && !this.isCompleted;
|
|
330
|
+
}
|
|
321
331
|
_addMonth() {
|
|
322
|
-
|
|
323
|
-
if (this.isCompleted) {
|
|
324
|
-
notify({ message: '종료된 프로젝트는 월을 추가할 수 없습니다. 기존 월 데이터만 수정할 수 있습니다.' });
|
|
332
|
+
if (this.isCompleted)
|
|
325
333
|
return;
|
|
326
|
-
}
|
|
327
334
|
const workDate = `${this.addYear}-${String(this.addMonth).padStart(2, '0')}`;
|
|
328
335
|
// 현재월 row 는 추가 금지 — 운영 원칙상 이번달 데이터는 입력 대상 아님.
|
|
329
336
|
const currentYm = moment().tz('Asia/Seoul').format('YYYY-MM');
|
|
@@ -347,6 +354,8 @@ let SvProjectCompleteTab4Monthly = class SvProjectCompleteTab4Monthly extends Li
|
|
|
347
354
|
}
|
|
348
355
|
async _removeMonth(rowIdx) {
|
|
349
356
|
var _a;
|
|
357
|
+
if (this.isCompleted)
|
|
358
|
+
return;
|
|
350
359
|
const row = this.monthRows[rowIdx];
|
|
351
360
|
// 신규 row (DB 미반영) — 메모리에서만 제거
|
|
352
361
|
if (row.isNew) {
|
|
@@ -420,6 +429,8 @@ let SvProjectCompleteTab4Monthly = class SvProjectCompleteTab4Monthly extends Li
|
|
|
420
429
|
*/
|
|
421
430
|
async _save() {
|
|
422
431
|
var _a, _b;
|
|
432
|
+
if (this.isCompleted)
|
|
433
|
+
return;
|
|
423
434
|
const patches = [];
|
|
424
435
|
for (const row of this.monthRows) {
|
|
425
436
|
if (!row.dirty)
|