@things-factory/worklist 10.1.173 → 10.1.174
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/components/activity-thread-timeline.js +3 -10
- package/dist-client/components/activity-thread-timeline.js.map +1 -1
- package/dist-client/components/timeline-entries.d.ts +38 -0
- package/dist-client/components/timeline-entries.js +61 -0
- package/dist-client/components/timeline-entries.js.map +1 -0
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
|
@@ -5,14 +5,9 @@ import { html, css, LitElement, nothing } from 'lit';
|
|
|
5
5
|
import { customElement, property } from 'lit/decorators.js';
|
|
6
6
|
import { i18next } from '@operato/i18n';
|
|
7
7
|
import { whenText } from '../when.js';
|
|
8
|
+
import { timelineEntries, whenOf } from './timeline-entries.js';
|
|
8
9
|
import { ActivityThread } from '../types/activity-thread.js';
|
|
9
10
|
import { entity360Name } from '@operato/layout';
|
|
10
|
-
/** 그 줄이 화면에 내보이는 시각. 정렬도 이 값으로 한다. */
|
|
11
|
-
function whenOf(item) {
|
|
12
|
-
const at = item?.terminatedAt ?? item?.updatedAt ?? item?.createdAt;
|
|
13
|
-
const ms = at === null || at === undefined || at === '' ? NaN : Date.parse(String(at));
|
|
14
|
-
return Number.isFinite(ms) ? ms : 0;
|
|
15
|
-
}
|
|
16
11
|
let ActivityThreadTimeline = class ActivityThreadTimeline extends LitElement {
|
|
17
12
|
static { this.styles = [
|
|
18
13
|
css `
|
|
@@ -236,10 +231,8 @@ let ActivityThreadTimeline = class ActivityThreadTimeline extends LitElement {
|
|
|
236
231
|
* 시각이 오르내리는 목록이 나왔다 — 11:51 승인됨 · 11:50 제출 · 11:50 게시 · 11:51 상신.
|
|
237
232
|
* 시간 순으로 읽으라고 만든 화면인데 시간 순이 아니었다.
|
|
238
233
|
*/
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
...activityApprovals.filter(item => item.judgment)
|
|
242
|
-
].sort((a, b) => (whenOf(a) > whenOf(b) ? 1 : -1));
|
|
234
|
+
/* 한 사람의 한 행동이 한 줄이 되게 거르고, 같은 시각이면 일어난 순서로 놓는다(timeline-entries). */
|
|
235
|
+
const timeline = timelineEntries(activityThreadHistories, activityApprovals, { shortForm: this.shortForm });
|
|
243
236
|
if (!timeline.length) {
|
|
244
237
|
return nothing;
|
|
245
238
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"activity-thread-timeline.js","sourceRoot":"","sources":["../../client/components/activity-thread-timeline.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@things-factory/organization/dist-client/component/approval-line-view.js'\n\nimport { html, css, LitElement, nothing } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport { i18next } from '@operato/i18n'\n\nimport { whenText } from '../when.js'\n\nimport { ActivityThread } from '../types/activity-thread.js'\nimport { ActivityThreadHistory } from '../types/activity-thread-history.js'\nimport { ActivityApproval } from '../types/activity-approval.js'\nimport { entity360Name } from '@operato/layout'\n\n\n/** 그 줄이 화면에 내보이는 시각. 정렬도 이 값으로 한다. */\nfunction whenOf(item: any): number {\n const at = item?.terminatedAt ?? item?.updatedAt ?? item?.createdAt\n\n const ms = at === null || at === undefined || at === '' ? NaN : Date.parse(String(at))\n\n return Number.isFinite(ms) ? ms : 0\n}\n\n@customElement('activity-thread-timeline')\nexport class ActivityThreadTimeline extends LitElement {\n static styles = [\n css`\n /*\n * 업무 하나가 지나온 자취 — **언제 · 무슨 일이 · 누구에 의해.**\n *\n * ── 무엇을 고쳤나 (2026-09-06) ─────────────────────────────────────\n * 1. 절 제목이 단추처럼 생겼었다. border: 2px 에 border-radius: 15px 인 알약이라\n * 누를 수 있는 것으로 보인다. 이 화면에서 알약은 거르개(chip)의 모양이다.\n * 2. 말풍선 꼬리에 #f4f4f4 가, 표 선에 lightgray 가 하드코딩되어 있었다. 어두운\n * 화면에서 밝은 회색이 그대로 나온다.\n * 3. 시각이 Tuesday, September 1, 2026 at 11:51 AM 이었다. when.ts 머리말 참조.\n *\n * ── 내용이 없는 줄에 상자를 그리지 않는다 ─────────────────────────\n * 의견이나 결과가 없는 줄에도 회색 상자가 min-height: 20px 으로 깔려 있었다. 저장 ·\n * 게시처럼 적을 것이 없는 기록이 대부분이라, 화면의 절반이 빈 회색 상자였다.\n */\n /*\n * 절 하나이므로 다른 절과 같은 카드로 둔다. 전에는 카드 밖에 그냥 놓여 있어서 「업무 정보」\n * 카드와 위계가 어긋났다 — 같은 층의 두 절인데 하나만 면 위에 있었다.\n */\n :host {\n display: block;\n padding: 14px;\n background: var(--ui-panel, #ffffff);\n border: 1px solid var(--ui-line, #d5dce0);\n border-radius: var(--ui-r, 10px);\n color: var(--ui-ink, #17242c);\n font-size: var(--ui-t-body, 13px);\n }\n\n /* 그릴 것이 없으면 빈 카드를 남기지 않는다. */\n :host(:not(:has(ol))) {\n display: none;\n }\n\n /*\n * ── 접어 둔다 (2026-09-06) ────────────────────────────────────────\n * 이력은 참고다. 결재자가 먼저 읽는 것은 「무엇을 승인하나」이고, 이력은 그것으로 판단이\n * 안 설 때 펴 보는 것이다. 그런데 이 카드가 내용 절과 똑같은 무게로 서 있었다.\n *\n * 접어 두되 **마지막에 무슨 일이 있었는지 한 줄은 남긴다.** 「이력 4건」만 있으면 펴 볼지\n * 말지를 정할 수가 없다.\n *\n * 펴 두어야 하는 화면이 있다 — 지난 일을 보러 온 화면(완료 조회), 그리고 보일 항목이 하나도\n * 없어서 「타임라인의 상신 사유를 보고 판단하세요」라고 안내한 경우다. 그때 접혀 있으면 그\n * 안내가 거짓말이 된다. 그래서 여는 쪽은 담는 화면이 정한다(open 속성).\n */\n h2 {\n margin: 0;\n font-size: var(--ui-t-title, 15px);\n font-weight: 600;\n color: var(--ui-ink, #17242c);\n }\n\n summary {\n display: flex;\n align-items: baseline;\n gap: var(--spacing-medium, 8px);\n cursor: pointer;\n list-style: none;\n }\n\n summary::-webkit-details-marker {\n display: none;\n }\n\n /* 펴고 접는 표시. 색만으로 말하지 않으려고 방향이 바뀐다. */\n .chevron {\n flex: none;\n font-size: 18px;\n color: var(--ui-muted, #57676f);\n transition: transform 0.12s ease;\n }\n\n details[open] .chevron {\n transform: rotate(90deg);\n }\n\n /* 접혔을 때 보이는 한 줄 — 마지막에 무슨 일이 있었나. */\n .last {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: var(--ui-t-small, 11.5px);\n color: var(--ui-faint, #8a959c);\n }\n\n details[open] .last {\n display: none;\n }\n\n .count {\n flex: none;\n font-size: var(--ui-t-small, 11.5px);\n color: var(--ui-muted, #57676f);\n font-variant-numeric: tabular-nums;\n }\n\n ol {\n list-style: none;\n margin: var(--spacing-medium, 8px) 0 0;\n padding: 0;\n }\n\n li {\n display: grid;\n grid-template-columns: max-content 16px 1fr;\n align-items: start;\n column-gap: var(--spacing-small, 4px);\n }\n\n /* 시각 — 자릿수를 맞춰 위아래를 바로 비교할 수 있게. */\n [date] {\n padding-top: 1px;\n font-family: var(--ui-mono, ui-monospace, Menlo, monospace);\n font-size: var(--ui-t-small, 11.5px);\n font-variant-numeric: tabular-nums;\n color: var(--ui-faint, #8a959c);\n white-space: nowrap;\n }\n\n /*\n * 점과 그 아래로 이어지는 세로선.\n *\n * 칸을 세로로 늘려(align-self: stretch) 그 안에 점과 선을 그린다. 전에는 칸이 9px 이라\n * 선 높이를 calc(100% + 100px) 으로 짐작해서 늘렸고, 줄 높이가 그보다 낮으면 선이 다음\n * 줄을 지나 카드 아래까지 흘러내렸다.\n */\n [status] {\n position: relative;\n align-self: stretch;\n width: 9px;\n margin-left: 3px;\n }\n\n [status]::after {\n content: '';\n position: absolute;\n top: 4px;\n left: 0;\n width: 9px;\n height: 9px;\n border-radius: 50%;\n background-color: var(--ui-accent, #00727e);\n }\n\n [status]::before {\n content: '';\n position: absolute;\n top: 15px;\n bottom: 0;\n left: 3px;\n width: 2px;\n background-color: var(--ui-line, #d5dce0);\n }\n\n li:last-child [status]::before {\n display: none;\n }\n\n [info] {\n min-width: 0;\n padding-bottom: var(--spacing-large, 16px);\n }\n\n li:last-child [info] {\n padding-bottom: 0;\n }\n\n /* 무슨 일이 있었나 — 이 줄에서 먼저 읽혀야 하는 값. */\n [what] {\n font-weight: 600;\n }\n\n /* 누가 — 곁말이다. 아이콘은 두지 않는다(이름 앞의 원형 아이콘이 값처럼 보였다). */\n [who] {\n margin-left: var(--spacing-small, 4px);\n font-size: var(--ui-t-small, 11.5px);\n color: var(--ui-faint, #8a959c);\n }\n\n /* 적힌 말 — 의견 · 사유 · 결과. 없으면 이 요소 자체를 그리지 않는다. */\n p {\n /* 줄을 바꿔 아래에 두되(block), 폭은 내용만큼만 — 짧은 의견에 화면 폭짜리 상자가 생겼다. */\n display: block;\n width: fit-content;\n max-width: 100%;\n margin: 5px 0 0;\n padding: 7px 10px;\n background: var(--ui-panel-2, #eef1f3);\n border-radius: var(--ui-r-small, 6px);\n color: var(--ui-ink, #17242c);\n font-size: var(--ui-t-small, 11.5px);\n line-height: 1.5;\n word-break: keep-all;\n }\n\n @media only screen and (max-width: 460px) {\n li {\n grid-template-columns: 16px 1fr;\n }\n\n /* 좁은 화면에서는 시각을 내용 아래로 내린다 — 한 줄의 절반을 시각에 내줄 수 없다. */\n [date] {\n grid-column: 2;\n grid-row: 2;\n padding: 0 0 var(--spacing-medium, 8px);\n }\n }\n `\n ]\n\n @property({ type: Object }) activityThread?: ActivityThread\n @property({ type: String }) subtitle?: string\n @property({ type: Boolean, attribute: 'short-form' }) shortForm?: boolean\n\n /**\n * 펴 둘 것인가 — **담는 화면이 정한다.**\n *\n * 기본은 접는다. 펴는 곳은 둘이다.\n * 완료 조회 지난 일을 보러 온 화면이라 이력이 곧 본문이다\n * 보일 항목 없음 화면이 「타임라인의 상신 사유를 보고 판단하세요」로 여기를 가리킨다\n */\n @property({ type: Boolean }) open?: boolean\n\n render() {\n const { activityApprovals = [], activityThreadHistories = [] } = this.activityThread || {}\n\n /*\n * **정렬 키와 표시 키가 같아야 한다.**\n *\n * 전에는 둘 다 updatedAt 으로 정렬해 놓고, 결재 줄만 terminatedAt 을 보여 주었다. 그래서\n * 시각이 오르내리는 목록이 나왔다 — 11:51 승인됨 · 11:50 제출 · 11:50 게시 · 11:51 상신.\n * 시간 순으로 읽으라고 만든 화면인데 시간 순이 아니었다.\n */\n const timeline = [\n ...activityThreadHistories.filter(\n item => item.transaction && (!this.shortForm || !/^.*(reject|approve|save|restart).*$/.test(item.transaction))\n ),\n ...activityApprovals.filter(item => item.judgment)\n ].sort((a, b) => (whenOf(a) > whenOf(b) ? 1 : -1))\n\n if (!timeline.length) {\n return nothing\n }\n\n return html`\n <details ?open=${this.open}>\n <summary>\n <md-icon class=\"chevron\">chevron_right</md-icon>\n <h2>${this.subtitle || i18next.t('label.timeline')}</h2>\n <span class=\"count\">${i18next.t('text.timeline-count', { count: timeline.length })}</span>\n <span class=\"last\">${this.lastLine(timeline[timeline.length - 1])}</span>\n </summary>\n\n <ol>\n ${timeline.map(item =>\n 'reason' in item\n ? this.renderActivityThreadHistory(item as ActivityThreadHistory)\n : this.renderActivityApproval(item as ActivityApproval)\n )}\n </ol>\n </details>\n `\n }\n\n /** 접혔을 때 보이는 한 줄 — 마지막 기록의 「무엇을 · 누가 · 언제」. */\n lastLine(item: any): string {\n const what =\n 'reason' in item\n ? i18next.t('label.activity-transaction-' + item.transaction)\n : i18next.t('label.activity-state-' + item.judgment)\n\n const who = item?.updater?.name || item?.approver?.name || ''\n\n return [what, who, whenText(whenOf(item))].filter(Boolean).join(' · ')\n }\n\n renderActivityThreadHistory(activityThreadHistory: ActivityThreadHistory) {\n const { updater, updatedAt, state, reason, output, transaction } = activityThreadHistory\n\n return html`\n <li>\n <span date>${whenText(updatedAt)}</span>\n <span status></span>\n\n <span info>\n <span what>${i18next.t('label.activity-transaction-' + transaction)}</span>\n ${updater?.name ? html`<span who>${entity360Name(updater)}</span>` : nothing}\n ${this.renderSaid(reason || (output ? this.renderOutput(output) : ''))}\n </span>\n </li>\n `\n }\n\n renderActivityApproval(activityApproval: ActivityApproval) {\n const { judgment, approver, comment, createdAt, terminatedAt } = activityApproval\n\n return html`\n <li>\n <span date>${whenText(terminatedAt ?? createdAt)}</span>\n <span status></span>\n <span info>\n <span what>${i18next.t('label.activity-state-' + judgment)}</span>\n ${approver?.name ? html`<span who>${entity360Name(approver)}</span>` : nothing}\n ${this.renderSaid(comment)}\n </span>\n </li>\n `\n }\n\n /** 적힌 말이 없으면 상자를 그리지 않는다 — 빈 회색 상자는 내용이 있는 줄과 구별되지 않는다. */\n renderSaid(said?: string) {\n return said ? html`<p>${said}</p>` : nothing\n }\n\n renderOutput(output: any) {\n const keys = Object.keys(output)\n if (keys.length === 0) {\n return ''\n }\n\n const firstKey = keys[0]\n let value = output[firstKey]\n\n // 값이 객체이거나 긴 문자열인 경우 처리\n if (typeof value === 'object') {\n value = Array.isArray(value) ? '[...]' : '{...}'\n } else if (typeof value === 'string' && value.length > 50) {\n value = value.substring(0, 47) + '...'\n }\n\n let result = `${firstKey}: ${value}`\n\n // 추가 속성이 있는 경우 표시\n if (keys.length > 1) {\n result += ' ' + i18next.t('text.and-n-more-fields', { n: keys.length - 1 })\n }\n\n return result\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"activity-thread-timeline.js","sourceRoot":"","sources":["../../client/components/activity-thread-timeline.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@things-factory/organization/dist-client/component/approval-line-view.js'\n\nimport { html, css, LitElement, nothing } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport { i18next } from '@operato/i18n'\n\nimport { whenText } from '../when.js'\nimport { timelineEntries, whenOf } from './timeline-entries.js'\n\nimport { ActivityThread } from '../types/activity-thread.js'\nimport { ActivityThreadHistory } from '../types/activity-thread-history.js'\nimport { ActivityApproval } from '../types/activity-approval.js'\nimport { entity360Name } from '@operato/layout'\n\n\n@customElement('activity-thread-timeline')\nexport class ActivityThreadTimeline extends LitElement {\n static styles = [\n css`\n /*\n * 업무 하나가 지나온 자취 — **언제 · 무슨 일이 · 누구에 의해.**\n *\n * ── 무엇을 고쳤나 (2026-09-06) ─────────────────────────────────────\n * 1. 절 제목이 단추처럼 생겼었다. border: 2px 에 border-radius: 15px 인 알약이라\n * 누를 수 있는 것으로 보인다. 이 화면에서 알약은 거르개(chip)의 모양이다.\n * 2. 말풍선 꼬리에 #f4f4f4 가, 표 선에 lightgray 가 하드코딩되어 있었다. 어두운\n * 화면에서 밝은 회색이 그대로 나온다.\n * 3. 시각이 Tuesday, September 1, 2026 at 11:51 AM 이었다. when.ts 머리말 참조.\n *\n * ── 내용이 없는 줄에 상자를 그리지 않는다 ─────────────────────────\n * 의견이나 결과가 없는 줄에도 회색 상자가 min-height: 20px 으로 깔려 있었다. 저장 ·\n * 게시처럼 적을 것이 없는 기록이 대부분이라, 화면의 절반이 빈 회색 상자였다.\n */\n /*\n * 절 하나이므로 다른 절과 같은 카드로 둔다. 전에는 카드 밖에 그냥 놓여 있어서 「업무 정보」\n * 카드와 위계가 어긋났다 — 같은 층의 두 절인데 하나만 면 위에 있었다.\n */\n :host {\n display: block;\n padding: 14px;\n background: var(--ui-panel, #ffffff);\n border: 1px solid var(--ui-line, #d5dce0);\n border-radius: var(--ui-r, 10px);\n color: var(--ui-ink, #17242c);\n font-size: var(--ui-t-body, 13px);\n }\n\n /* 그릴 것이 없으면 빈 카드를 남기지 않는다. */\n :host(:not(:has(ol))) {\n display: none;\n }\n\n /*\n * ── 접어 둔다 (2026-09-06) ────────────────────────────────────────\n * 이력은 참고다. 결재자가 먼저 읽는 것은 「무엇을 승인하나」이고, 이력은 그것으로 판단이\n * 안 설 때 펴 보는 것이다. 그런데 이 카드가 내용 절과 똑같은 무게로 서 있었다.\n *\n * 접어 두되 **마지막에 무슨 일이 있었는지 한 줄은 남긴다.** 「이력 4건」만 있으면 펴 볼지\n * 말지를 정할 수가 없다.\n *\n * 펴 두어야 하는 화면이 있다 — 지난 일을 보러 온 화면(완료 조회), 그리고 보일 항목이 하나도\n * 없어서 「타임라인의 상신 사유를 보고 판단하세요」라고 안내한 경우다. 그때 접혀 있으면 그\n * 안내가 거짓말이 된다. 그래서 여는 쪽은 담는 화면이 정한다(open 속성).\n */\n h2 {\n margin: 0;\n font-size: var(--ui-t-title, 15px);\n font-weight: 600;\n color: var(--ui-ink, #17242c);\n }\n\n summary {\n display: flex;\n align-items: baseline;\n gap: var(--spacing-medium, 8px);\n cursor: pointer;\n list-style: none;\n }\n\n summary::-webkit-details-marker {\n display: none;\n }\n\n /* 펴고 접는 표시. 색만으로 말하지 않으려고 방향이 바뀐다. */\n .chevron {\n flex: none;\n font-size: 18px;\n color: var(--ui-muted, #57676f);\n transition: transform 0.12s ease;\n }\n\n details[open] .chevron {\n transform: rotate(90deg);\n }\n\n /* 접혔을 때 보이는 한 줄 — 마지막에 무슨 일이 있었나. */\n .last {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: var(--ui-t-small, 11.5px);\n color: var(--ui-faint, #8a959c);\n }\n\n details[open] .last {\n display: none;\n }\n\n .count {\n flex: none;\n font-size: var(--ui-t-small, 11.5px);\n color: var(--ui-muted, #57676f);\n font-variant-numeric: tabular-nums;\n }\n\n ol {\n list-style: none;\n margin: var(--spacing-medium, 8px) 0 0;\n padding: 0;\n }\n\n li {\n display: grid;\n grid-template-columns: max-content 16px 1fr;\n align-items: start;\n column-gap: var(--spacing-small, 4px);\n }\n\n /* 시각 — 자릿수를 맞춰 위아래를 바로 비교할 수 있게. */\n [date] {\n padding-top: 1px;\n font-family: var(--ui-mono, ui-monospace, Menlo, monospace);\n font-size: var(--ui-t-small, 11.5px);\n font-variant-numeric: tabular-nums;\n color: var(--ui-faint, #8a959c);\n white-space: nowrap;\n }\n\n /*\n * 점과 그 아래로 이어지는 세로선.\n *\n * 칸을 세로로 늘려(align-self: stretch) 그 안에 점과 선을 그린다. 전에는 칸이 9px 이라\n * 선 높이를 calc(100% + 100px) 으로 짐작해서 늘렸고, 줄 높이가 그보다 낮으면 선이 다음\n * 줄을 지나 카드 아래까지 흘러내렸다.\n */\n [status] {\n position: relative;\n align-self: stretch;\n width: 9px;\n margin-left: 3px;\n }\n\n [status]::after {\n content: '';\n position: absolute;\n top: 4px;\n left: 0;\n width: 9px;\n height: 9px;\n border-radius: 50%;\n background-color: var(--ui-accent, #00727e);\n }\n\n [status]::before {\n content: '';\n position: absolute;\n top: 15px;\n bottom: 0;\n left: 3px;\n width: 2px;\n background-color: var(--ui-line, #d5dce0);\n }\n\n li:last-child [status]::before {\n display: none;\n }\n\n [info] {\n min-width: 0;\n padding-bottom: var(--spacing-large, 16px);\n }\n\n li:last-child [info] {\n padding-bottom: 0;\n }\n\n /* 무슨 일이 있었나 — 이 줄에서 먼저 읽혀야 하는 값. */\n [what] {\n font-weight: 600;\n }\n\n /* 누가 — 곁말이다. 아이콘은 두지 않는다(이름 앞의 원형 아이콘이 값처럼 보였다). */\n [who] {\n margin-left: var(--spacing-small, 4px);\n font-size: var(--ui-t-small, 11.5px);\n color: var(--ui-faint, #8a959c);\n }\n\n /* 적힌 말 — 의견 · 사유 · 결과. 없으면 이 요소 자체를 그리지 않는다. */\n p {\n /* 줄을 바꿔 아래에 두되(block), 폭은 내용만큼만 — 짧은 의견에 화면 폭짜리 상자가 생겼다. */\n display: block;\n width: fit-content;\n max-width: 100%;\n margin: 5px 0 0;\n padding: 7px 10px;\n background: var(--ui-panel-2, #eef1f3);\n border-radius: var(--ui-r-small, 6px);\n color: var(--ui-ink, #17242c);\n font-size: var(--ui-t-small, 11.5px);\n line-height: 1.5;\n word-break: keep-all;\n }\n\n @media only screen and (max-width: 460px) {\n li {\n grid-template-columns: 16px 1fr;\n }\n\n /* 좁은 화면에서는 시각을 내용 아래로 내린다 — 한 줄의 절반을 시각에 내줄 수 없다. */\n [date] {\n grid-column: 2;\n grid-row: 2;\n padding: 0 0 var(--spacing-medium, 8px);\n }\n }\n `\n ]\n\n @property({ type: Object }) activityThread?: ActivityThread\n @property({ type: String }) subtitle?: string\n @property({ type: Boolean, attribute: 'short-form' }) shortForm?: boolean\n\n /**\n * 펴 둘 것인가 — **담는 화면이 정한다.**\n *\n * 기본은 접는다. 펴는 곳은 둘이다.\n * 완료 조회 지난 일을 보러 온 화면이라 이력이 곧 본문이다\n * 보일 항목 없음 화면이 「타임라인의 상신 사유를 보고 판단하세요」로 여기를 가리킨다\n */\n @property({ type: Boolean }) open?: boolean\n\n render() {\n const { activityApprovals = [], activityThreadHistories = [] } = this.activityThread || {}\n\n /*\n * **정렬 키와 표시 키가 같아야 한다.**\n *\n * 전에는 둘 다 updatedAt 으로 정렬해 놓고, 결재 줄만 terminatedAt 을 보여 주었다. 그래서\n * 시각이 오르내리는 목록이 나왔다 — 11:51 승인됨 · 11:50 제출 · 11:50 게시 · 11:51 상신.\n * 시간 순으로 읽으라고 만든 화면인데 시간 순이 아니었다.\n */\n /* 한 사람의 한 행동이 한 줄이 되게 거르고, 같은 시각이면 일어난 순서로 놓는다(timeline-entries). */\n const timeline = timelineEntries(activityThreadHistories, activityApprovals, { shortForm: this.shortForm })\n\n if (!timeline.length) {\n return nothing\n }\n\n return html`\n <details ?open=${this.open}>\n <summary>\n <md-icon class=\"chevron\">chevron_right</md-icon>\n <h2>${this.subtitle || i18next.t('label.timeline')}</h2>\n <span class=\"count\">${i18next.t('text.timeline-count', { count: timeline.length })}</span>\n <span class=\"last\">${this.lastLine(timeline[timeline.length - 1])}</span>\n </summary>\n\n <ol>\n ${timeline.map(item =>\n 'reason' in item\n ? this.renderActivityThreadHistory(item as ActivityThreadHistory)\n : this.renderActivityApproval(item as ActivityApproval)\n )}\n </ol>\n </details>\n `\n }\n\n /** 접혔을 때 보이는 한 줄 — 마지막 기록의 「무엇을 · 누가 · 언제」. */\n lastLine(item: any): string {\n const what =\n 'reason' in item\n ? i18next.t('label.activity-transaction-' + item.transaction)\n : i18next.t('label.activity-state-' + item.judgment)\n\n const who = item?.updater?.name || item?.approver?.name || ''\n\n return [what, who, whenText(whenOf(item))].filter(Boolean).join(' · ')\n }\n\n renderActivityThreadHistory(activityThreadHistory: ActivityThreadHistory) {\n const { updater, updatedAt, state, reason, output, transaction } = activityThreadHistory\n\n return html`\n <li>\n <span date>${whenText(updatedAt)}</span>\n <span status></span>\n\n <span info>\n <span what>${i18next.t('label.activity-transaction-' + transaction)}</span>\n ${updater?.name ? html`<span who>${entity360Name(updater)}</span>` : nothing}\n ${this.renderSaid(reason || (output ? this.renderOutput(output) : ''))}\n </span>\n </li>\n `\n }\n\n renderActivityApproval(activityApproval: ActivityApproval) {\n const { judgment, approver, comment, createdAt, terminatedAt } = activityApproval\n\n return html`\n <li>\n <span date>${whenText(terminatedAt ?? createdAt)}</span>\n <span status></span>\n <span info>\n <span what>${i18next.t('label.activity-state-' + judgment)}</span>\n ${approver?.name ? html`<span who>${entity360Name(approver)}</span>` : nothing}\n ${this.renderSaid(comment)}\n </span>\n </li>\n `\n }\n\n /** 적힌 말이 없으면 상자를 그리지 않는다 — 빈 회색 상자는 내용이 있는 줄과 구별되지 않는다. */\n renderSaid(said?: string) {\n return said ? html`<p>${said}</p>` : nothing\n }\n\n renderOutput(output: any) {\n const keys = Object.keys(output)\n if (keys.length === 0) {\n return ''\n }\n\n const firstKey = keys[0]\n let value = output[firstKey]\n\n // 값이 객체이거나 긴 문자열인 경우 처리\n if (typeof value === 'object') {\n value = Array.isArray(value) ? '[...]' : '{...}'\n } else if (typeof value === 'string' && value.length > 50) {\n value = value.substring(0, 47) + '...'\n }\n\n let result = `${firstKey}: ${value}`\n\n // 추가 속성이 있는 경우 표시\n if (keys.length > 1) {\n result += ' ' + i18next.t('text.and-n-more-fields', { n: keys.length - 1 })\n }\n\n return result\n }\n}\n"]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a thread's timeline lists, and in what order - one line per thing a person did.
|
|
3
|
+
*
|
|
4
|
+
* A thread keeps two records of one decision: the approval row (who judged, what they wrote) and
|
|
5
|
+
* the thread history the judgment wrote (`approve` · `reject` · `escalate`, with the same comment
|
|
6
|
+
* as its reason). Listing both drew an approval by 경영지원 as 「상신 정연희」 and 「승인됨 정연희」
|
|
7
|
+
* with the comment twice (2026-10-16) - and 「상신」, the word `escalate` carries, read as if the
|
|
8
|
+
* approver had put the request up. The approval row is the judgment; the history line is dropped.
|
|
9
|
+
*
|
|
10
|
+
* Putting something up writes `post` and `submit` at the same moment, so one 상신 showed as
|
|
11
|
+
* 「제출」 and 「게시」 - in either order, the time being equal. `post` is dropped where the thread
|
|
12
|
+
* was submitted; a tie in time keeps the order things happen in.
|
|
13
|
+
*
|
|
14
|
+
* Kept free of imports so the test runner can hold it.
|
|
15
|
+
*/
|
|
16
|
+
type HistoryLine = {
|
|
17
|
+
transaction?: string;
|
|
18
|
+
reason?: any;
|
|
19
|
+
updatedAt?: any;
|
|
20
|
+
createdAt?: any;
|
|
21
|
+
terminatedAt?: any;
|
|
22
|
+
};
|
|
23
|
+
type ApprovalLine = {
|
|
24
|
+
judgment?: string;
|
|
25
|
+
updatedAt?: any;
|
|
26
|
+
createdAt?: any;
|
|
27
|
+
terminatedAt?: any;
|
|
28
|
+
};
|
|
29
|
+
/** The moment a line shows - and is sorted by. */
|
|
30
|
+
export declare function whenOf(item: any): number;
|
|
31
|
+
/**
|
|
32
|
+
* `shortForm` is every screen's timeline today. Without it every history line stays - the long form
|
|
33
|
+
* is the record as stored - and only the order of lines at one moment is set.
|
|
34
|
+
*/
|
|
35
|
+
export declare function timelineEntries<H extends HistoryLine, A extends ApprovalLine>(histories: readonly H[], approvals: readonly A[], { shortForm }?: {
|
|
36
|
+
shortForm?: boolean;
|
|
37
|
+
}): (H | A)[];
|
|
38
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a thread's timeline lists, and in what order - one line per thing a person did.
|
|
3
|
+
*
|
|
4
|
+
* A thread keeps two records of one decision: the approval row (who judged, what they wrote) and
|
|
5
|
+
* the thread history the judgment wrote (`approve` · `reject` · `escalate`, with the same comment
|
|
6
|
+
* as its reason). Listing both drew an approval by 경영지원 as 「상신 정연희」 and 「승인됨 정연희」
|
|
7
|
+
* with the comment twice (2026-10-16) - and 「상신」, the word `escalate` carries, read as if the
|
|
8
|
+
* approver had put the request up. The approval row is the judgment; the history line is dropped.
|
|
9
|
+
*
|
|
10
|
+
* Putting something up writes `post` and `submit` at the same moment, so one 상신 showed as
|
|
11
|
+
* 「제출」 and 「게시」 - in either order, the time being equal. `post` is dropped where the thread
|
|
12
|
+
* was submitted; a tie in time keeps the order things happen in.
|
|
13
|
+
*
|
|
14
|
+
* Kept free of imports so the test runner can hold it.
|
|
15
|
+
*/
|
|
16
|
+
/** History lines an approval row already says, or that are no step anyone took. */
|
|
17
|
+
const NOT_SHOWN = ['approve', 'reject', 'escalate', 'save', 'restart'];
|
|
18
|
+
/** The order things happen in on one thread, for lines at the same moment. Approval rows sit at 4; an unknown step with them. */
|
|
19
|
+
const STEP_ORDER = {
|
|
20
|
+
post: 0,
|
|
21
|
+
start: 1,
|
|
22
|
+
delegate: 2,
|
|
23
|
+
submit: 3,
|
|
24
|
+
/* the judgment's own lines follow the approval row that made them */
|
|
25
|
+
approve: 5,
|
|
26
|
+
reject: 5,
|
|
27
|
+
escalate: 5,
|
|
28
|
+
end: 6,
|
|
29
|
+
abort: 6
|
|
30
|
+
};
|
|
31
|
+
const APPROVAL_ORDER = 4;
|
|
32
|
+
/** The moment a line shows - and is sorted by. */
|
|
33
|
+
export function whenOf(item) {
|
|
34
|
+
const at = item?.terminatedAt ?? item?.updatedAt ?? item?.createdAt;
|
|
35
|
+
const ms = at === null || at === undefined || at === '' ? NaN : Date.parse(String(at));
|
|
36
|
+
return Number.isFinite(ms) ? ms : 0;
|
|
37
|
+
}
|
|
38
|
+
function isHistory(item) {
|
|
39
|
+
return 'reason' in item;
|
|
40
|
+
}
|
|
41
|
+
function stepOf(item) {
|
|
42
|
+
return isHistory(item) ? STEP_ORDER[String(item.transaction)] ?? APPROVAL_ORDER : APPROVAL_ORDER;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* `shortForm` is every screen's timeline today. Without it every history line stays - the long form
|
|
46
|
+
* is the record as stored - and only the order of lines at one moment is set.
|
|
47
|
+
*/
|
|
48
|
+
export function timelineEntries(histories, approvals, { shortForm = false } = {}) {
|
|
49
|
+
const submitted = histories.some(item => item.transaction === 'submit');
|
|
50
|
+
const shown = (item) => !shortForm ||
|
|
51
|
+
(!NOT_SHOWN.some(word => String(item.transaction).includes(word)) && !(item.transaction === 'post' && submitted));
|
|
52
|
+
const lines = [
|
|
53
|
+
...histories.filter(item => !!item.transaction && shown(item)),
|
|
54
|
+
...approvals.filter(item => item.judgment)
|
|
55
|
+
];
|
|
56
|
+
return lines
|
|
57
|
+
.map((item, at) => ({ item, at }))
|
|
58
|
+
.sort((a, b) => whenOf(a.item) - whenOf(b.item) || stepOf(a.item) - stepOf(b.item) || a.at - b.at)
|
|
59
|
+
.map(({ item }) => item);
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=timeline-entries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timeline-entries.js","sourceRoot":"","sources":["../../client/components/timeline-entries.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What a thread's timeline lists, and in what order - one line per thing a person did.\n *\n * A thread keeps two records of one decision: the approval row (who judged, what they wrote) and\n * the thread history the judgment wrote (`approve` · `reject` · `escalate`, with the same comment\n * as its reason). Listing both drew an approval by 경영지원 as 「상신 정연희」 and 「승인됨 정연희」\n * with the comment twice (2026-10-16) - and 「상신」, the word `escalate` carries, read as if the\n * approver had put the request up. The approval row is the judgment; the history line is dropped.\n *\n * Putting something up writes `post` and `submit` at the same moment, so one 상신 showed as\n * 「제출」 and 「게시」 - in either order, the time being equal. `post` is dropped where the thread\n * was submitted; a tie in time keeps the order things happen in.\n *\n * Kept free of imports so the test runner can hold it.\n */\n\n/** History lines an approval row already says, or that are no step anyone took. */\nconst NOT_SHOWN = ['approve', 'reject', 'escalate', 'save', 'restart']\n\n/** The order things happen in on one thread, for lines at the same moment. Approval rows sit at 4; an unknown step with them. */\nconst STEP_ORDER: Record<string, number> = {\n post: 0,\n start: 1,\n delegate: 2,\n submit: 3,\n /* the judgment's own lines follow the approval row that made them */\n approve: 5,\n reject: 5,\n escalate: 5,\n end: 6,\n abort: 6\n}\nconst APPROVAL_ORDER = 4\n\ntype HistoryLine = { transaction?: string; reason?: any; updatedAt?: any; createdAt?: any; terminatedAt?: any }\ntype ApprovalLine = { judgment?: string; updatedAt?: any; createdAt?: any; terminatedAt?: any }\n\n/** The moment a line shows - and is sorted by. */\nexport function whenOf(item: any): number {\n const at = item?.terminatedAt ?? item?.updatedAt ?? item?.createdAt\n\n const ms = at === null || at === undefined || at === '' ? NaN : Date.parse(String(at))\n\n return Number.isFinite(ms) ? ms : 0\n}\n\nfunction isHistory(item: any): item is HistoryLine {\n return 'reason' in item\n}\n\nfunction stepOf(item: HistoryLine | ApprovalLine): number {\n return isHistory(item) ? STEP_ORDER[String(item.transaction)] ?? APPROVAL_ORDER : APPROVAL_ORDER\n}\n\n/**\n * `shortForm` is every screen's timeline today. Without it every history line stays - the long form\n * is the record as stored - and only the order of lines at one moment is set.\n */\nexport function timelineEntries<H extends HistoryLine, A extends ApprovalLine>(\n histories: readonly H[],\n approvals: readonly A[],\n { shortForm = false }: { shortForm?: boolean } = {}\n): (H | A)[] {\n const submitted = histories.some(item => item.transaction === 'submit')\n const shown = (item: H) =>\n !shortForm ||\n (!NOT_SHOWN.some(word => String(item.transaction).includes(word)) && !(item.transaction === 'post' && submitted))\n\n const lines = [\n ...histories.filter(item => !!item.transaction && shown(item)),\n ...approvals.filter(item => item.judgment)\n ]\n\n return lines\n .map((item, at) => ({ item, at }))\n .sort((a, b) => whenOf(a.item) - whenOf(b.item) || stepOf(a.item) - stepOf(b.item) || a.at - b.at)\n .map(({ item }) => item)\n}\n"]}
|