@things-factory/auth-ui 10.1.26 → 10.1.27
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/abstract-auth-page.js +2 -0
- package/dist-client/components/abstract-auth-page.js.map +1 -1
- package/dist-client/components/abstract-password-reset.js +2 -0
- package/dist-client/components/abstract-password-reset.js.map +1 -1
- package/dist-client/entries/auth/activate.js +2 -0
- package/dist-client/entries/auth/activate.js.map +1 -1
- package/dist-client/entries/auth/checkin.js +2 -0
- package/dist-client/entries/auth/checkin.js.map +1 -1
- package/dist-client/entries/auth/result.js +2 -0
- package/dist-client/entries/auth/result.js.map +1 -1
- package/dist-client/entries/oauth2/oauth2-decision-error-page.js +2 -0
- package/dist-client/entries/oauth2/oauth2-decision-error-page.js.map +1 -1
- package/dist-client/entries/oauth2/oauth2-decision-page.js +2 -0
- package/dist-client/entries/oauth2/oauth2-decision-page.js.map +1 -1
- package/dist-client/entries/public/home.js +2 -0
- package/dist-client/entries/public/home.js.map +1 -1
- package/dist-client/entry-control-size-styles.d.ts +15 -0
- package/dist-client/entry-control-size-styles.js +37 -0
- package/dist-client/entry-control-size-styles.js.map +1 -0
- package/dist-client/pages/app-binding/app-bindings.d.ts +0 -2
- package/dist-client/pages/app-binding/app-bindings.js +1 -6
- package/dist-client/pages/app-binding/app-bindings.js.map +1 -1
- package/dist-client/pages/appliance/home.d.ts +0 -2
- package/dist-client/pages/appliance/home.js +1 -6
- package/dist-client/pages/appliance/home.js.map +1 -1
- package/dist-client/pages/application/applications.d.ts +0 -2
- package/dist-client/pages/application/applications.js +1 -6
- package/dist-client/pages/application/applications.js.map +1 -1
- package/dist-client/pages/attribute/attribute-set-item-list.js +2 -1
- package/dist-client/pages/attribute/attribute-set-item-list.js.map +1 -1
- package/dist-client/pages/attribute/attribute-set-management.d.ts +0 -5
- package/dist-client/pages/attribute/attribute-set-management.js +3 -13
- package/dist-client/pages/attribute/attribute-set-management.js.map +1 -1
- package/dist-client/pages/auth-provider/auth-provider-management.d.ts +0 -2
- package/dist-client/pages/auth-provider/auth-provider-management.js +1 -6
- package/dist-client/pages/auth-provider/auth-provider-management.js.map +1 -1
- package/dist-client/pages/domain/domain-management.d.ts +0 -2
- package/dist-client/pages/domain/domain-management.js +1 -6
- package/dist-client/pages/domain/domain-management.js.map +1 -1
- package/dist-client/pages/domain-owner/domain-owner-management.d.ts +0 -2
- package/dist-client/pages/domain-owner/domain-owner-management.js +1 -6
- package/dist-client/pages/domain-owner/domain-owner-management.js.map +1 -1
- package/dist-client/pages/env-var/env-var-list-page.d.ts +0 -2
- package/dist-client/pages/env-var/env-var-list-page.js +1 -6
- package/dist-client/pages/env-var/env-var-list-page.js.map +1 -1
- package/dist-client/pages/partner/partner-management.d.ts +1 -0
- package/dist-client/pages/partner/partner-management.js +36 -13
- package/dist-client/pages/partner/partner-management.js.map +1 -1
- package/dist-client/pages/role/role-list-page.js +0 -1
- package/dist-client/pages/role/role-list-page.js.map +1 -1
- package/dist-client/pages/user/user-list-page.js +0 -1
- package/dist-client/pages/user/user-list-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -4
- package/tests/entry-control-sizes.test.ts +91 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { __decorate, __metadata } from "tslib";
|
|
2
2
|
import '@things-factory/component-ui';
|
|
3
|
+
import '@operato/list-state/ox-page-state.js';
|
|
3
4
|
import '../../components/partner-info-card.js';
|
|
4
5
|
import '../../components/partner-role-editor.js';
|
|
5
6
|
import '../../components/invite-customer.js';
|
|
6
7
|
import gql from 'graphql-tag';
|
|
7
8
|
import { css, html } from 'lit';
|
|
8
|
-
import { customElement, property } from 'lit/decorators.js';
|
|
9
|
+
import { customElement, property, state } from 'lit/decorators.js';
|
|
9
10
|
import { client, gqlContext } from '@operato/graphql';
|
|
10
11
|
import { i18next } from '@operato/i18n';
|
|
11
12
|
import { PageView } from '@operato/shell';
|
|
@@ -14,6 +15,10 @@ let PartnerManagement = class PartnerManagement extends PageView {
|
|
|
14
15
|
super(...arguments);
|
|
15
16
|
this.customers = [];
|
|
16
17
|
this.vendors = [];
|
|
18
|
+
/** The first read; a refresh keeps the partners already on screen. */
|
|
19
|
+
this.loading = false;
|
|
20
|
+
/** Why the partners could not be read. It was dropped before, so a failure looked like no partners. */
|
|
21
|
+
this.loadError = null;
|
|
17
22
|
}
|
|
18
23
|
static { this.styles = [
|
|
19
24
|
css `
|
|
@@ -23,10 +28,6 @@ let PartnerManagement = class PartnerManagement extends PageView {
|
|
|
23
28
|
padding: var(--spacing-large);
|
|
24
29
|
|
|
25
30
|
overflow: auto;
|
|
26
|
-
}
|
|
27
|
-
h2 { margin: var(--title-margin);
|
|
28
|
-
font: var(--title-font);
|
|
29
|
-
color: var(--title-text-color);
|
|
30
31
|
}
|
|
31
32
|
.role-editor { flex: 1;
|
|
32
33
|
}
|
|
@@ -42,8 +43,7 @@ let PartnerManagement = class PartnerManagement extends PageView {
|
|
|
42
43
|
[i18next.t('label.vendors')]: this.vendors || []
|
|
43
44
|
};
|
|
44
45
|
return html `
|
|
45
|
-
<
|
|
46
|
-
|
|
46
|
+
<ox-page-state .loading=${this.loading} .error=${this.loadError} @retry=${() => this.refreshPartners()}>
|
|
47
47
|
<quick-find-list
|
|
48
48
|
.data="${data}"
|
|
49
49
|
.contentRenderer="${(item, currentTabKey) => html `
|
|
@@ -64,6 +64,7 @@ let PartnerManagement = class PartnerManagement extends PageView {
|
|
|
64
64
|
.customers=${this.customers || []}
|
|
65
65
|
@invitationCompleted="${this.refreshPartners.bind(this)}"
|
|
66
66
|
></invite-customer>
|
|
67
|
+
</ox-page-state>
|
|
67
68
|
`;
|
|
68
69
|
}
|
|
69
70
|
pageUpdated() {
|
|
@@ -74,7 +75,10 @@ let PartnerManagement = class PartnerManagement extends PageView {
|
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
async refreshPartners() {
|
|
77
|
-
|
|
78
|
+
this.loading = !this.customers.length && !this.vendors.length && !this.loadError;
|
|
79
|
+
let response;
|
|
80
|
+
try {
|
|
81
|
+
response = await client.query({ query: gql `
|
|
78
82
|
query { customers { id
|
|
79
83
|
name
|
|
80
84
|
description
|
|
@@ -85,12 +89,23 @@ let PartnerManagement = class PartnerManagement extends PageView {
|
|
|
85
89
|
}
|
|
86
90
|
}
|
|
87
91
|
`,
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
this.
|
|
92
|
+
context: gqlContext()
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
this.loadError = error;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
this.loading = false;
|
|
93
101
|
}
|
|
102
|
+
if (response.errors?.length) {
|
|
103
|
+
this.loadError = response.errors;
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
this.loadError = null;
|
|
107
|
+
this.customers = response.data.customers;
|
|
108
|
+
this.vendors = response.data.vendors;
|
|
94
109
|
}
|
|
95
110
|
};
|
|
96
111
|
__decorate([
|
|
@@ -101,6 +116,14 @@ __decorate([
|
|
|
101
116
|
property({ type: Array }),
|
|
102
117
|
__metadata("design:type", Array)
|
|
103
118
|
], PartnerManagement.prototype, "vendors", void 0);
|
|
119
|
+
__decorate([
|
|
120
|
+
state(),
|
|
121
|
+
__metadata("design:type", Object)
|
|
122
|
+
], PartnerManagement.prototype, "loading", void 0);
|
|
123
|
+
__decorate([
|
|
124
|
+
state(),
|
|
125
|
+
__metadata("design:type", Object)
|
|
126
|
+
], PartnerManagement.prototype, "loadError", void 0);
|
|
104
127
|
PartnerManagement = __decorate([
|
|
105
128
|
customElement('partner-management')
|
|
106
129
|
], PartnerManagement);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"partner-management.js","sourceRoot":"","sources":["../../../client/pages/partner/partner-management.ts"],"names":[],"mappings":";AAAA,OAAO,8BAA8B,CAAA;AACrC,OAAO,uCAAuC,CAAA;AAC9C,OAAO,yCAAyC,CAAA;AAChD,OAAO,qCAAqC,CAAA;AAE5C,OAAO,GAAG,MAAM,aAAa,CAAA;AAC7B,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAA;AAC/B,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"partner-management.js","sourceRoot":"","sources":["../../../client/pages/partner/partner-management.ts"],"names":[],"mappings":"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","sourcesContent":["import '@things-factory/component-ui'\nimport '@operato/list-state/ox-page-state.js'\nimport '../../components/partner-info-card.js'\nimport '../../components/partner-role-editor.js'\nimport '../../components/invite-customer.js'\n\nimport gql from 'graphql-tag'\nimport { css, html } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { client, gqlContext } from '@operato/graphql'\nimport { i18next } from '@operato/i18n'\nimport { PageView } from '@operato/shell'\n\n@customElement('partner-management')\nclass PartnerManagement extends PageView { static styles = [\n css`\n :host { display: flex;\n flex-direction: column;\n background-color: var(--md-sys-color-background);\n padding: var(--spacing-large);\n\n overflow: auto;\n }\n .role-editor { flex: 1;\n }\n `\n ]\n\n @property({ type: Array }) customers: any[] = []\n @property({ type: Array }) vendors: any[] = []\n /** The first read; a refresh keeps the partners already on screen. */\n @state() loading = false\n /** Why the partners could not be read. It was dropped before, so a failure looked like no partners. */\n @state() loadError: unknown = null\n\n get context() { return { title: `${i18next.t('title.partners')}`,\n help: 'operato-hub/partners'\n }\n }\n\n render() { const data = { [i18next.t('label.customers')]: this.customers || [],\n [i18next.t('label.vendors')]: this.vendors || []\n }\n\n return html`\n <ox-page-state .loading=${this.loading} .error=${this.loadError} @retry=${() => this.refreshPartners()}>\n <quick-find-list\n .data=\"${data}\"\n .contentRenderer=\"${(item, currentTabKey) => html`\n ${currentTabKey === i18next.t('label.customers')\n ? html`\n <partner-role-editor\n class=\"role-editor\"\n .customer=\"${item}\"\n @contractTerminated=\"${this.refreshPartners.bind(this)}\"\n ></partner-role-editor>\n `\n : html`<partner-info-card .partner=\"${item}\"></partner-info-card>`}\n `}\"\n >\n </quick-find-list>\n\n <invite-customer\n .customers=${this.customers || []}\n @invitationCompleted=\"${this.refreshPartners.bind(this)}\"\n ></invite-customer>\n </ox-page-state>\n `\n }\n\n pageUpdated() { if (this.active) { this.refreshPartners()\n } else { /* this page is deactivated */\n }\n }\n\n async refreshPartners() {\n this.loading = !this.customers.length && !this.vendors.length && !this.loadError\n let response: any\n try {\n response = await client.query({ query: gql`\n query { customers { id\n name\n description\n }\n vendors { id\n name\n description\n }\n }\n `,\n context: gqlContext()\n })\n } catch (error) {\n this.loadError = error\n return\n } finally {\n this.loading = false\n }\n\n if (response.errors?.length) {\n this.loadError = response.errors\n return\n }\n\n this.loadError = null\n this.customers = response.data.customers\n this.vendors = response.data.vendors\n }\n}\n"]}
|
|
@@ -279,7 +279,6 @@ let RoleListPage = class RoleListPage extends RoleListPageBase {
|
|
|
279
279
|
<md-icon>add</md-icon>
|
|
280
280
|
</button>
|
|
281
281
|
</div>
|
|
282
|
-
<ox-context-page-toolbar class="actions" .context=${this.context}></ox-context-page-toolbar>
|
|
283
282
|
</div>
|
|
284
283
|
|
|
285
284
|
<ox-grist-personalizer slot="setting"></ox-grist-personalizer>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role-list-page.js","sourceRoot":"","sources":["../../../client/pages/role/role-list-page.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@operato/data-grist'\nimport '@operato/oops/ox-oops-note.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, nothing } from 'lit'\nimport type { CSSResultGroup } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\n\nimport { client, gqlContext } from '@operato/graphql'\nimport { openPopup } from '@operato/layout'\nimport { OxPrompt } from '@operato/popup/ox-prompt.js'\nimport { i18next, localize } from '@operato/i18n'\nimport { p13n } from '@operato/p13n'\nimport { InheritedValueType, navigate, PageView } from '@operato/shell'\nimport { CommonGristStyles, CommonHeaderStyles, ScrollbarStyles } from '@operato/styles'\nimport { isMobileDevice } from '@operato/utils'\nimport { getDomain } from '@things-factory/auth-base/dist-client'\n\nimport type { DataGrist, FetchOption } from '@operato/data-grist'\n\n/**\n * 역할 목록 — **보이는 것과 고칠 수 있는 것을 가른다.**\n *\n * ── 옛 화면이 하던 것 (`pages/role/role-management.ts`) ──────────────────────\n * `quick-find-list` 안에 역할을 늘어놓고, 펼치면 그 안에 이름 입력칸과 84줄의 권한 체크박스가\n * 함께 있었다. 그 구조에서 셋이 났다.\n *\n * ① 이름 입력이 매 글자마다 페이지로 올라가 **모든** 편집기에 공유됐다 — A 를 고치고 B 를\n * 저장하면 B 의 이름이 A 가 됐다. 지금은 이름·설명을 격자에서 고친다.\n * ② 목록에 권한이 하나도 안 보였다. 「이 역할이 무엇을 여나」를 알려면 펼쳐야 했다.\n * ③ 한도도 쪽 나누기도 없었다.\n *\n * ── 상속이 이 화면의 중심이다 ───────────────────────────────────────────────\n * 목록은 부모 도메인의 역할까지 보여 준다 — **부여할 수 있는 범위가 그것**이기 때문이다.\n * 그런데 그 행은 여기서 못 고친다(`updateRole` 이 `domain.id` 하나로 좁혀 찾는다).\n *\n * 전에는 `Include` 로 열어서 **못 고치는 9행이 고칠 수 있는 2행 앞에 깔렸다**(UX 실측).\n * 지금은 「우리 역할」로 열고, 세 묶음의 수를 함께 적어 **상속이 있다는 사실 자체는 남긴다.**\n * 편집도 `editable` 함수로 상속 행에서 닫고, 삭제도 상속 행을 안 보낸다.\n *\n * ── 권한은 수만 낸다 ────────────────────────────────────────────────────────\n * 전에는 `activity 쓰기 · activity-instance 쓰기·읽기` 식으로 잘린 코드 용어를 그렸고, 그것을\n * 그리려고 권한 **행**을 받았다(UX 실측 5,731 bytes · 69ms 대 518 bytes · 7ms). 지금은\n * `privilegeCount` 하나를 받는다. 권한 편집으로 가는 문은 **버튼 gutter 칸**이 진다 —\n * 눌리는 숫자는 손잡이로 보이지 않는다.\n */\nconst RoleListPageBase = p13n(localize(i18next)(PageView))\n\n@customElement('role-list-page')\nexport class RoleListPage extends RoleListPageBase {\n static styles: CSSResultGroup = [\n ScrollbarStyles,\n CommonGristStyles,\n CommonHeaderStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n width: 100%;\n background-color: var(--md-sys-color-surface);\n color: var(--md-sys-color-on-surface);\n position: relative;\n }\n\n ox-grist {\n flex: 1;\n min-height: 0;\n }\n\n /* 요약 배지 — 색은 짝으로, 그리고 「무엇이 나를 기다리나」에만 쓴다. */\n .badge {\n display: inline-block;\n padding: 2px 8px;\n border-radius: var(--md-sys-shape-corner-small, 6px);\n font-size: 0.78rem;\n white-space: nowrap;\n background-color: var(--md-sys-color-surface-variant);\n color: var(--md-sys-color-on-surface-variant);\n }\n .badge[attention] {\n background-color: var(--md-sys-color-error-container);\n color: var(--md-sys-color-on-error-container);\n }\n /* 「우리」는 기본 상태다 — 색을 쓰면 색이 아무것도 말하지 않게 된다. */\n .badge[quiet] {\n background-color: transparent;\n color: var(--md-sys-color-outline);\n }\n\n /*\n ── 세 묶음 ────────────────────────────────────────────────────────────\n 고른 것은 면으로 말한다. 글자색만 바꾸면 야간에 안 보인다.\n */\n [scopes] {\n display: flex;\n align-items: center;\n gap: 4px;\n flex-wrap: wrap;\n }\n\n /*\n 어디에서 물려받나 — 단추가 아니라 **사실을 적은 글**이다. 그래서 알약도 테두리도 없다.\n 고르는 것과 읽는 것이 같은 모양이면 눌러 보게 된다.\n\n 단추 줄에서 한 칸 떨어뜨린다. 붙여 두면 네 번째 단추로 보인다.\n */\n [lineage] {\n margin-left: 8px;\n font-size: 0.78rem;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n button[scope] {\n display: inline-flex;\n align-items: baseline;\n gap: 5px;\n padding: 5px 12px;\n font: var(--label-font, inherit);\n font-size: 0.82rem;\n color: var(--md-sys-color-on-surface-variant);\n background-color: transparent;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: var(--md-sys-shape-corner-full, 999px);\n cursor: pointer;\n white-space: nowrap;\n }\n\n button[scope]:hover {\n background-color: var(--md-sys-color-surface-variant);\n }\n\n button[scope][active] {\n color: var(--md-sys-color-on-secondary-container);\n background-color: var(--md-sys-color-secondary-container);\n border-color: var(--md-sys-color-secondary-container);\n }\n\n button[scope] [count] {\n font-size: 0.78rem;\n color: var(--md-sys-color-outline);\n }\n\n button[scope][active] [count] {\n color: var(--md-sys-color-on-secondary-container);\n }\n\n /*\n * 새 역할 단추 — **옆에 선 것들을 따라간다.**\n *\n * 이 줄에는 알약 범위 단추 셋과 검색이 서 있다. 그 사이에서 이것만 네모난 기본 단추면\n * 같은 줄의 문법이 깨진다. 범위 단추와 같은 모서리·같은 테두리를 쓰되, **여는 일**이므로\n * 채워서 한 단 앞으로 낸다 — 나머지는 거르는 일이고 이것만 만드는 일이다.\n */\n button[add] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 5px 10px;\n color: var(--md-sys-color-on-secondary-container);\n background-color: var(--md-sys-color-secondary-container);\n border: 1px solid transparent;\n border-radius: var(--md-sys-shape-corner-full, 999px);\n cursor: pointer;\n margin-left: auto;\n }\n\n button[add]:hover {\n background-color: var(--md-sys-color-secondary);\n color: var(--md-sys-color-on-secondary);\n }\n\n button[add] md-icon {\n --md-icon-size: 18px;\n }\n\n ox-oops-note[note] {\n position: absolute;\n inset: 0;\n z-index: 5;\n background-color: var(--md-sys-color-surface);\n }\n `\n ]\n\n @property({ type: Object }) config: any\n @property({ type: String }) mode: 'CARD' | 'GRID' | 'LIST' = isMobileDevice() ? 'LIST' : 'GRID'\n\n /* 읽는 중 · 못 읽었다 · 읽었는데 없다 를 가른다. */\n @state() private fetchState: 'loading' | 'failed' | 'loaded' = 'loading'\n @state() private total: number = 0\n @state() private filtered: boolean = false\n\n /*\n * 지금 어느 도메인에 있나.\n *\n * 목록은 상속으로 부모 도메인의 역할까지 보여 준다 — 실제로 부여할 수 있는 범위가 그것이기\n * 때문이다. 그런데 **그 행은 여기서 못 고친다**(`updateRole`·`updateMultipleRole` 이\n * `domain.id` 하나로 좁혀 찾는다). 그래서 편집을 열어 두면 안 된다.\n *\n * 프로필의 `domain` 에는 `id` 가 없어서(`auth-private-process-router.ts`) `subdomain` 으로\n * 견준다 — 두 도메인이 같은 subdomain 을 가질 수 없으므로 id 와 같은 답이다.\n */\n @state() private mySubdomain: string = ''\n\n /**\n * 물려받는 곳. `null` 이면 최상위, `undefined` 면 아직 안 읽었다.\n *\n * 셋을 가르는 이유는 화면이 「모른다」와 「없다」를 다르게 그려야 하기 때문이다.\n */\n @state() private parentDomain: { name?: string; subdomain?: string } | null | undefined = undefined\n\n /*\n * **어느 묶음을 보고 있나 — 기본값은 「우리 역할」이다.**\n *\n * 전에는 `Include` 로 열었다. 그러면 처음 화면에 **못 고치는 행 9개가 앞에 깔린다**\n * (UX 실측: miratek 2 · system 9). `None` 으로 열면 처음 화면이 2행이고 **그 2행이 전부\n * 고칠 수 있는 행**이다.\n *\n * 세 묶음은 서버에 이미 있다(`InheritedValueType`). 새로 만들 것이 없다.\n */\n @state() private scope: InheritedValueType = InheritedValueType.None\n\n /**\n * 묶음마다 몇 건인가.\n *\n * 수를 함께 적는 이유는 **상속이 있다는 사실 자체가 보이게** 하는 것이다. 기본값을 「우리\n * 역할」로 좁혀 놓으면, 수가 없으면 상속 9건이 존재하는지조차 모른다.\n *\n * `undefined` 는 「아직 못 셌다」다 — 0 으로 메우면 「없다」와 같아 보인다.\n */\n @state() private counts: Partial<Record<InheritedValueType, number>> = {}\n\n @query('ox-grist') private grist!: DataGrist\n\n /**\n * 저장과 삭제를 **툴바**에 둔다.\n *\n * 격자에서 고치는 화면들의 관례다(`fiscal-quarter-list-page` 외). 격자 안 편집은 여러 줄을\n * 한꺼번에 고치므로, 줄마다 저장 단추를 두면 어디를 눌러야 하는지가 사라진다.\n */\n /**\n * 찾기는 **제목 줄**에 선다 — 셸이 그 자리를 모든 화면에 같은 모양으로 둔다.\n *\n * 이 페이지는 한동안 `ox-filters-form` 을 헤드룸의 칩 줄에 **직접** 끼워 넣고 있었다. 그래서\n * 돋보기가 범위 칩과 만들기 단추 사이에 끼어, 같은 앱의 다른 목록과 찾기 자리가 달랐다\n * (`env-var-list-page` 외 auth-ui 목록 다섯이 전부 아래 모양이다).\n *\n * 둘은 대안이 아니라 **다른 것**이다 — `search` 는 목록 전체를 훑는 글자 찾기이고,\n * `filter` 는 칸별 조건 폼을 접었다 펴는 것이다. 표준 화면은 둘 다 선언한다.\n */\n get context() {\n return {\n title: i18next.t('text.role_management'),\n help: 'auth/roles',\n search: {\n handler: (search: string) => {\n this.grist.searchText = search\n },\n value: this.grist?.searchText || ''\n },\n filter: {\n handler: () => {\n this.grist.toggleHeadroom()\n }\n },\n actions: [\n { title: i18next.t('button.save'), action: this.onSaveGrid.bind(this) },\n { title: i18next.t('button.delete'), action: this.onDeleteGrid.bind(this) }\n ]\n }\n }\n\n render() {\n return html`\n <ox-grist\n .mode=${this.mode}\n .config=${this.config}\n .fetchHandler=${this.fetchHandler.bind(this)}\n .personalConfigProvider=${this.getPagePreferenceProvider('ox-grist')!}\n >\n <div slot=\"headroom\" class=\"header\">\n <div class=\"filters\">\n ${this.renderScopes()}\n\n <!-- 찾기는 제목 줄이 갖는다(context.search). 폼이 자기 검색칸을 또 그리면 둘이 된다. -->\n <ox-filters-form without-search></ox-filters-form>\n\n <!--\n 여기 ox-record-creator 가 있었다 — 목록에 빈 줄을 만들고 이름을 치게 하는\n 관례(24 파일)다. 걷어낸 이유는 openCreate 의 머리말에 있다.\n -->\n <button add @click=${() => this.openCreate()} title=${i18next.t('button.create')}>\n <md-icon>add</md-icon>\n </button>\n </div>\n <ox-context-page-toolbar class=\"actions\" .context=${this.context}></ox-context-page-toolbar>\n </div>\n\n <ox-grist-personalizer slot=\"setting\"></ox-grist-personalizer>\n </ox-grist>\n\n ${this.renderNote()}\n `\n }\n\n /**\n * 세 묶음.\n *\n * **수를 함께 적는 것이 이 부품의 이유다.** 기본값을 「우리 역할」로 좁혀 두면 처음 화면이\n * 깨끗한 대신 **상속 9건이 있다는 사실이 사라진다.** 수가 그것을 되돌려 놓는다.\n *\n * 아직 못 센 묶음은 수를 **안 그린다** — 0 으로 메우면 「없다」와 같아 보인다.\n */\n private renderScopes() {\n const scopes: [InheritedValueType, string][] = [\n [InheritedValueType.None, 'label.ours'],\n [InheritedValueType.Only, 'label.inherited'],\n [InheritedValueType.Include, 'label.all']\n ]\n\n return html`\n <div scopes>\n ${scopes.map(\n ([scope, key]) => html`\n <button scope ?active=${this.scope === scope} @click=${() => this.onScope(scope)}>\n ${i18next.t(key)}\n ${this.counts[scope] === undefined ? nothing : html`<span count>${this.counts[scope]}</span>`}\n </button>\n `\n )}\n ${this.renderLineage()}\n </div>\n `\n }\n\n /**\n * 어디에서 물려받나 — **있을 때나 없을 때나 같은 자리에서 말한다.**\n *\n * 「상속」 단추만 두면 눌러 보기 전에는 그것이 무엇을 뜻하는지 알 수 없다. 부모가 있는\n * 도메인에서는 **어느 도메인인지**가 필요하고(그 역할을 고치러 가야 할 곳이다), 최상위\n * 도메인에서는 **물려받는 곳이 없다는 사실**이 필요하다. 후자를 안 적으면 「상속 0」이\n * 「아직 안 만들었나」로 읽힌다.\n *\n * 단추를 숨기지 않는다. 숨기면 최상위 도메인의 관리자는 상속이라는 것이 있다는 사실\n * 자체를 모르고, 자식 도메인을 만든 뒤에 갑자기 나타나는 단추를 만난다.\n *\n * 아직 프로필을 못 읽었으면 **아무것도 안 적는다** — 모르는 것을 「없다」로 적지 않는다.\n */\n private renderLineage() {\n if (this.parentDomain === undefined) {\n return nothing\n }\n\n const from = this.parentDomain?.name || this.parentDomain?.subdomain\n\n return html`\n <span lineage>\n ${from\n ? i18next.t('text.inherited from x', { x: from })\n : i18next.t('text.this domain is top level and inherits from nowhere')}\n </span>\n `\n }\n\n private onScope(scope: InheritedValueType) {\n if (this.scope === scope) {\n return\n }\n\n this.scope = scope\n this.grist.fetch()\n }\n\n /**\n * 세 묶음의 건수.\n *\n * 세 번 묻는다. 한 번에 셋을 세는 질의가 서버에 없고, 각각 `total` 만 받으므로 가볍다\n * (UX 실측 158·566·684 bytes). 실패하면 **수를 안 그린다** — 틀린 수를 그리는 것보다\n * 없는 것이 낫다.\n */\n private async countScopes() {\n const scopes = [InheritedValueType.None, InheritedValueType.Only, InheritedValueType.Include]\n\n const counted = await Promise.all(\n scopes.map(async scope => {\n const response = await client.query({\n query: gql`\n query ($inherited: InheritedValueType) {\n roles(inherited: $inherited, pagination: { page: 1, limit: 1 }) {\n total\n }\n }\n `,\n variables: { inherited: scope }\n })\n\n return [scope, response.errors?.length ? undefined : response.data?.roles?.total] as const\n })\n )\n\n this.counts = counted.reduce((counts, [scope, total]) => {\n if (total !== undefined) {\n counts[scope] = total\n }\n return counts\n }, {} as Partial<Record<InheritedValueType, number>>)\n }\n\n /**\n * 목록 위에 겹쳐 그린다 — 격자를 떼면 다시 읽을 길이 없어진다.\n *\n * **아무 역할도 없는 것이 이 화면의 가장 중요한 자리다.** 새 도메인은 여기서 시작하고,\n * 역할이 없으면 초대한 사람은 로그인해도 볼 화면이 하나도 없다. 그래서 그 사실을 말한다.\n */\n private renderNote() {\n if (this.fetchState === 'loading') {\n return nothing\n }\n\n if (this.fetchState === 'failed') {\n return html`<ox-oops-note\n note\n icon=\"error_outline\"\n title=${i18next.t('text.failed to fetch')}\n description=${i18next.t('text.please try again')}\n ></ox-oops-note>`\n }\n\n if (this.total === 0 && !this.filtered) {\n return html`<ox-oops-note\n note\n icon=\"badge\"\n title=${i18next.t('text.no role yet')}\n description=${i18next.t('text.without a role they see nothing')}\n ></ox-oops-note>`\n }\n\n return nothing\n }\n\n /**\n * 상속된 역할인가 — **격자의 편집 가드가 이것을 본다.**\n *\n * `mySubdomain` 을 아직 못 읽었으면 **거짓을 돌려주지 않고 상속으로 본다.** 「모른다」를\n * 「내 것이다」로 바꾸면 그 순간 못 고치는 행이 열리고, 사람이 타이핑해 저장한 뒤에 서버가\n * 거절한다. 모를 때는 잠가 두는 쪽이 안전하다.\n */\n private isInherited(record: any): boolean {\n if (!this.mySubdomain) {\n return true\n }\n\n const subdomain = record?.domain?.subdomain\n return Boolean(subdomain) && subdomain !== this.mySubdomain\n }\n\n /**\n * 출처 한 칸.\n *\n * 「우리」는 조용히 둔다 — 기본 상태에 색을 쓰면 색이 아무것도 말하지 않게 된다.\n * 상속은 **어느 도메인에서 왔는지 함께 적는다.** 「상속」만 적으면 관리자가 어디로 가야\n * 하는지 모른다.\n */\n private sourceCell(record: any) {\n if (!this.isInherited(record)) {\n return html`<span class=\"badge\" quiet>${i18next.t('label.ours')}</span>`\n }\n\n const from = record?.domain?.name || record?.domain?.subdomain || ''\n return html`<span class=\"badge\" title=${i18next.t('text.role belongs to x domain and is edited there', { x: from })}\n >${i18next.t('label.inherited')}${from ? ` · ${from}` : ''}</span\n >`\n }\n\n async pageInitialized() {\n const mine: any = await getDomain()\n\n this.mySubdomain = mine?.subdomain || ''\n\n /*\n * `undefined` 는 「아직 모른다」, `null` 은 「부모가 없다」다. 프로필이 `parent` 칸을\n * 들고 오므로 둘이 갈린다 — 옛 서버가 그 칸을 안 보내면 `undefined` 로 남아 아무것도\n * 안 적는다. 「모른다」를 「없다」로 접지 않는다.\n */\n this.parentDomain = mine && 'parent' in mine ? mine.parent : undefined\n\n this.countScopes()\n\n this.config = {\n /*\n * **두 모드가 다른 말을 하지 않게 한다.**\n *\n * ⚠ 여기가 `summary`·`domain` 을 가리킨 채로 남아 있었다 — 컬럼을 `privilegeCount`·\n * `source` 로 갈았는데 이 설정을 안 고쳤다. 그래서 **LIST 모드에서는 권한 수도 출처도\n * 아예 안 그려졌다.** 이름이 틀려도 격자가 조용히 넘어가므로 빌드도 시험도 못 잡는다.\n *\n * 그리고 이 앱이 실제로 LIST 로 연다(`isMobileDevice()` 또는 사람마다 기억된 개인 설정).\n * GRID 만 보고 만들면 그 사람들은 내가 만든 칸을 한 번도 못 본다.\n */\n list: {\n fields: ['name', 'description'],\n details: ['privilegeCount', 'source']\n },\n columns: [\n { type: 'gutter', gutterName: 'sequence', fixed: true },\n {\n /*\n * **권한 편집으로 가는 문.**\n *\n * `gutterName: 'button'` 이 이 저장소의 관례다 — 68 파일 · 104 자리\n * (`employee-list-page.ts:110-124` 가 같은 모양). `header` 에 이름이 붙은 아이콘 칸은\n * **손잡이로 보인다.** 그것이 「권한 편집 방법을 모르겠다」를 고치는 자리다.\n *\n * 행 전체에 클릭을 두지 않는 이유는 그대로다 — 이름을 고치려 누른 것이 화면을\n * 떠나면 안 된다.\n *\n * ── 상속 행에서도 버튼은 남긴다 ─────────────────────────────────────\n * **읽는 것은 정당하다.** 상속 역할이 무엇을 여는지 보는 것은 막을 이유가 없고,\n * 오히려 「우리 역할로 복사」를 하려면 먼저 봐야 한다. `icon`·`title` 이 record 를\n * 받으므로 그 구분이 여기 실린다 — 글자만 「보기만」으로 바뀐다.\n */\n type: 'gutter',\n gutterName: 'button',\n header: i18next.t('field.privileges'),\n /*\n * ⚠ **아직 저장 안 된 빈 줄에는 내주지 않는다.** 격자가 데이터 2행 아래에 추가용 빈\n * 줄을 하나 더 그리는데, 거기에도 버튼이 나왔다. 눌러도 `record.id` 가 없어 아무 일이\n * 없다 — **조용한 무동작 단추**다.\n *\n * `employee-list-page` 가 `icon` 에서 `''` 를 돌려주는 이유가 이것이었다. 모양만\n * 베끼고 그 가드를 안 가져왔다.\n */\n icon: (record: any) => (record?.id ? 'key' : ''),\n iconOnly: false,\n title: (record: any) =>\n !record?.id\n ? ''\n : this.isInherited(record)\n ? i18next.t('text.view only')\n : i18next.t('button.edit privileges'),\n width: 92,\n handlers: {\n click: (_columns: any, _data: any, _column: any, record: any) => {\n if (record?.id) {\n navigate(`role-privileges/${record.id}`)\n }\n }\n }\n },\n {\n type: 'string',\n name: 'name',\n header: i18next.t('field.name'),\n /*\n * **격자 안에서 고친다.** 이 저장소의 관례가 그렇고(`editable` 139 파일 대\n * `openPopup` 96 파일), 옛 화면이 이름을 팝업에 두었다가 A 의 이름을 B 에 저장했다.\n *\n * ⚠ **상속된 행은 열지 않는다.** `editable` 이 함수를 받는다\n * (`data-grist` types.ts:458, data-grid-body.js:530·861 이 그렇게 부른다). 이것이\n * 없으면 부모 도메인 역할에 타이핑해 저장할 수 있고, 서버가 그때 거절한다.\n */\n record: { align: 'left', editable: (_v: any, _c: any, record: any) => !this.isInherited(record) },\n filter: 'search',\n sortable: true,\n width: 160\n },\n {\n type: 'string',\n name: 'description',\n header: i18next.t('field.description'),\n record: { align: 'left', editable: (_v: any, _c: any, record: any) => !this.isInherited(record) },\n filter: 'search',\n sortable: true,\n width: 220\n },\n {\n /*\n * **이 역할이 여는 것이 몇 개인가.** 읽는 값이고, 누르는 자리가 아니다.\n *\n * 전에는 여기에 `activity 쓰기 · activity-instance 쓰기·읽기 · 3개 더` 식으로 잘린\n * 코드 용어를 그렸다. 그것을 그리려고 목록이 권한 **행**을 받았다.\n *\n * ```\n * [UX 실측] roles(limit:20) 권한 포함 5,731 bytes · 69ms\n * [UX 실측] 권한 없이 518 bytes · 7ms → 11.1배 · 10배\n * ```\n *\n * 수는 잘린 문자열이 못 하던 것을 한다 — 「더 있다」가 아니라 **얼마인지** 말하고,\n * `0` 이면 아직 아무것도 안 열린 역할이라는 것이 한눈에 보인다.\n *\n * ⚠ **여기에 클릭을 얹지 않는다.** 한때 그렇게 뒀는데, 읽는 값과 누르는 자리가 한 칸에\n * 들어간다. 그리고 사용자 평가의 첫 줄이 「권한 편집 방법을 모르겠다」였는데 —\n * **눌리는 숫자는 보이는 손잡이가 아니다.** 문은 아래 버튼 칸이 진다.\n */\n type: 'string',\n name: 'privilegeCount',\n header: i18next.t('field.privileges'),\n record: { align: 'center', renderer: privilegeCountCell },\n width: 110\n },\n {\n /*\n * **출처 — 여기가 「보이는 것과 고칠 수 있는 것」을 가른다.**\n *\n * 전에는 도메인 이름을 날값으로 냈다(`SYSTEM` · `MIRATEK`). 그건 구분이 아니다 —\n * 읽는 사람이 자기 도메인 이름을 외우고 있어야 한다. 지금은 「우리」와\n * 「상속 · SYSTEM」 으로 말한다.\n *\n * ⚠ `classifier` 로 행 전체를 물들이지 않는다. 선례들이 색을 `'#8B0000'` 처럼 박아\n * 두었고(dataset 5곳), 그 규약(`emphasized: [배경, 글자]`)이 `var(...)` 를 받는지도\n * 확인되지 않았다. 그리고 `emphasized` 는 **강조**하는 장치인데 여기서 필요한 것은\n * 그 반대다 — 11행 중 9행을 강조하면 강조가 아니다. 그래서 이 칸이 진다.\n */\n type: 'string',\n name: 'source',\n header: i18next.t('field.source'),\n record: { align: 'center', renderer: (_v: any, _c: any, record: any) => this.sourceCell(record) },\n width: 150\n },\n {\n type: 'datetime',\n name: 'updatedAt',\n header: i18next.t('field.updated_at'),\n record: { align: 'center' },\n sortable: true,\n width: 160\n }\n ],\n rows: {\n selectable: { multiple: true }\n },\n sorters: [{ name: 'name' }]\n }\n }\n\n async pageUpdated(_changes: any, _lifecycle: any) {\n if (this.active) {\n await this.updateComplete\n this.grist.fetch()\n }\n }\n\n /**\n * 역할을 가져온다 — **권한 행은 받지 않는다.**\n *\n * 전에는 `privileges { id name category }` 를 함께 물었다. 그것으로 그린 것은 잘린 코드 용어\n * 셋이었고, 값은 이랬다.\n *\n * ```\n * [UX 실측] 권한 포함 5,731 bytes · 69ms\n * [UX 실측] 권한 없이 518 bytes · 7ms\n * ```\n *\n * 바이트보다 큰 것은 **`Role.privileges` 필드 리졸버가 역할마다 한 번씩 도는 것**이 함께\n * 없어지는 쪽이다. 지금은 `privilegeCount` 를 받는다 — DB 가 센다(`role-query.ts`).\n *\n * `inherited` 는 화면이 고른 묶음을 그대로 넘긴다. 기본값이 「우리 역할」이라 처음 화면에\n * 못 고치는 행이 안 깔린다.\n */\n async fetchHandler({ filters = [], page, limit, sortings = [] }: FetchOption) {\n this.fetchState = 'loading'\n this.filtered = filters.length > 0\n\n try {\n const response = (\n await client.query({\n query: gql`\n query (\n $filters: [Filter!]\n $pagination: Pagination\n $sortings: [Sorting!]\n $inherited: InheritedValueType\n ) {\n responses: roles(\n filters: $filters\n pagination: $pagination\n sortings: $sortings\n inherited: $inherited\n ) {\n items {\n id\n name\n description\n domain {\n id\n name\n subdomain\n }\n privilegeCount\n updatedAt\n }\n total\n }\n }\n `,\n variables: {\n filters,\n pagination: { page, limit },\n sortings: sortings.length ? sortings : [{ name: 'name' }],\n inherited: this.scope\n }\n })\n ).data.responses\n\n this.total = response.total\n this.fetchState = 'loaded'\n\n return {\n records: response.items,\n total: response.total\n }\n } catch (e) {\n /* 못 읽은 것을 「없다」로 그리지 않는다. */\n this.fetchState = 'failed'\n throw e\n }\n }\n\n /**\n * 격자에서 고친 줄들을 저장한다.\n *\n * 이 저장소의 목록 관례를 그대로 쓴다 — `dirtyRecords` 를 `__dirtyfields__[key].after` 로 펴서\n * `cuFlag` 와 함께 보낸다(`fiscal-quarter-list-page` 가 같은 모양). 서버가\n * `updateMultipleRole` 로 받는다.\n *\n * **`privileges` 는 안 보낸다.** 여기서 고치는 것은 이름과 설명이고, 권한은 전용 화면이\n * 소유한다. 안 보낸 것을 「비우라」로 읽으면 이름만 고친 순간 그 역할이 아무것도 안 여는\n * 역할이 된다 — 서버 쪽에도 같은 말을 주석으로 적어 뒀다.\n */\n private async onSaveGrid() {\n const dirty = this.grist.dirtyRecords\n if (!dirty?.length) {\n return\n }\n\n const patches = dirty.map((record: any) => {\n const patch: any = record.id ? { id: record.id } : {}\n for (const key in record.__dirtyfields__) {\n patch[key] = record.__dirtyfields__[key].after\n }\n patch.cuFlag = record.__dirty__\n return patch\n })\n\n const response = await client.mutate({\n mutation: gql`\n mutation ($patches: [RolePatch!]!) {\n updateMultipleRole(patches: $patches) {\n id\n }\n }\n `,\n variables: { patches },\n context: gqlContext()\n })\n\n if (response.errors?.length) {\n /* 실패를 조용히 넘기지 않는다 — 격자는 고친 채로 남아 있어야 다시 저장할 수 있다. */\n document.dispatchEvent(\n new CustomEvent('notify', { detail: { level: 'error', message: response.errors[0]?.message } })\n )\n return\n }\n\n this.grist.fetch()\n }\n\n /**\n * 고른 역할을 지운다.\n *\n * 서버는 **가진 사람이 있는 역할을 남긴다**(`deleteRoles`). 그래서 여기서 「지웠다」고 말하지\n * 않고 목록을 다시 읽는다 — 무엇이 남았는지가 화면에서 보이는 것이 맞다.\n */\n private async onDeleteGrid() {\n const selected = this.grist.selected\n if (!selected?.length) {\n return\n }\n\n /*\n * **상속된 행은 여기서 못 지운다** — `deleteRoles` 가 `domain.id` 로 좁혀 찾으므로 그 행은\n * 조용히 남는다. 「지웠다」로 보이고 목록에 그대로 있으면 사람이 두 번 세 번 누른다.\n */\n const mine = selected.filter((record: any) => !this.isInherited(record))\n if (!mine.length) {\n await OxPrompt.open({\n type: 'warning',\n title: i18next.t('button.delete'),\n text: i18next.t('text.inherited roles are edited in their own domain'),\n confirmButton: { text: i18next.t('button.confirm') }\n })\n return\n }\n\n if (\n !(await OxPrompt.open({\n type: 'warning',\n title: i18next.t('button.delete'),\n text: i18next.t('text.roles in use are kept'),\n confirmButton: { text: i18next.t('button.confirm') },\n cancelButton: { text: i18next.t('button.cancel') }\n }))\n ) {\n return\n }\n\n const response = await client.mutate({\n mutation: gql`\n mutation ($ids: [String!]!) {\n deleteRoles(ids: $ids)\n }\n `,\n variables: { ids: mine.map((record: any) => record.id) },\n context: gqlContext()\n })\n\n if (response.errors?.length) {\n document.dispatchEvent(\n new CustomEvent('notify', { detail: { level: 'error', message: response.errors[0]?.message } })\n )\n return\n }\n\n this.grist.fetch()\n }\n\n /**\n * 역할을 만드는 두 문을 **한 자리에** 연다.\n *\n * ── 왜 `ox-record-creator`(빈 줄)를 걷어냈나 ────────────────────────────────\n * 이름을 짓는 것은 쉬운 쪽이고 「품질검사원에게 84개 중 무엇을 주나」가 어려운 쪽인데, 빈\n * 줄은 그 판단을 통째로 사람에게 넘긴다. 미라텍이 그래서 역할 둘뿐이다.\n *\n * 그리고 그 단추는 `.filters` 안에 있었는데 `CommonHeaderStyles` 는 `.actions button` 만\n * 꾸민다. `.filters` 에 button 규칙이 없어서 **브라우저 기본 단추**가 그대로 나왔다 — 옆의\n * 알약 범위 단추·검색과 같은 줄에서 혼자 네모난 회색 상자였다. 이 관례를 쓰는 다른 목록\n * 화면(24 파일)도 전부 같은 상태다.\n *\n * ── 순서가 가르친다 ────────────────────────────────────────────────────────\n * 템플릿이 먼저고 백지가 나중이다. 「품질검사원, 권한 7」을 본 사람은 자리가 어떻게 생긴\n * 것인지 알고 나서 자기 것을 짓는다. 그렇다고 백지를 감추면 안 된다 — 「야간반장」·「외주\n * 검사」 같은 자리는 그 공장이 아는 것이지 제품이 아는 게 아니다.\n */\n private async openCreate() {\n await import('../../components/role-template-picker.js')\n\n const popup = openPopup(\n html`<role-template-picker .page=${this} @close=${() => popup.close()}></role-template-picker>`,\n {\n backdrop: true,\n size: 'large',\n title: i18next.t('button.create role')\n }\n )\n }\n\n /** 템플릿에서 — 이름은 이 공장이 정한 것을 그대로 보낸다. */\n async createFromTemplate(templateId: string, name: string) {\n const response = await client.mutate({\n mutation: gql`\n mutation ($templateId: String!, $name: String) {\n createRoleFromTemplate(templateId: $templateId, name: $name) {\n id\n }\n }\n `,\n variables: { templateId, name },\n context: gqlContext()\n })\n\n if (response.errors?.length) {\n document.dispatchEvent(\n new CustomEvent('notify', { detail: { level: 'error', message: response.errors[0]?.message } })\n )\n return false\n }\n\n this.grist.fetch()\n return true\n }\n\n /** 백지에서 — 권한 없이 선다. 목록의 권한 수 칸이 0 을 빨갛게 말하므로 다음 걸음이 보인다. */\n async createBlankRole(name: string, description: string) {\n return await this.onCreateRole({ name, description })\n }\n\n /**\n * 새 역할.\n *\n * **권한 없이 만들어진다.** 그것이 맞다 — 이름을 정하는 것과 무엇을 열지 정하는 것은 다른\n * 판단이고, 목록의 요약 칸이 「여는 것이 없음」을 빨갛게 말하므로 다음 걸음이 보인다.\n */\n private async onCreateRole(record: any) {\n const response = await client.mutate({\n mutation: gql`\n mutation ($role: NewRole!) {\n createRole(role: $role) {\n id\n }\n }\n `,\n variables: { role: { name: record.name, description: record.description } },\n context: gqlContext()\n })\n\n if (response.errors?.length) {\n document.dispatchEvent(\n new CustomEvent('notify', { detail: { level: 'error', message: response.errors[0]?.message } })\n )\n return false\n }\n\n this.grist.fetch()\n return true\n }\n}\n\n/**\n * 권한 수 한 칸 — **0 이 가장 할 말이 많은 값이다.**\n *\n * 권한이 하나도 없는 역할은 붙여도 아무 일이 없다. 그것이 이 화면에서 **조치가 필요한 유일한\n * 상태**이므로 색을 그때만 쓴다. 나머지는 수만 낸다.\n */\nfunction privilegeCountCell(_value: any, _column: any, record: any) {\n const count = record?.privilegeCount\n\n /* 「모른다」와 「0」은 다르다 — 아직 안 온 값에 색을 쓰지 않는다. */\n if (count === undefined || count === null) {\n return html``\n }\n\n return count === 0\n ? html`<span class=\"badge\" attention>${i18next.t('text.no privilege granted')}</span>`\n : html`<span class=\"badge\">${count}</span>`\n}\n"]}
|
|
1
|
+
{"version":3,"file":"role-list-page.js","sourceRoot":"","sources":["../../../client/pages/role/role-list-page.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@operato/data-grist'\nimport '@operato/oops/ox-oops-note.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, nothing } from 'lit'\nimport type { CSSResultGroup } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\n\nimport { client, gqlContext } from '@operato/graphql'\nimport { openPopup } from '@operato/layout'\nimport { OxPrompt } from '@operato/popup/ox-prompt.js'\nimport { i18next, localize } from '@operato/i18n'\nimport { p13n } from '@operato/p13n'\nimport { InheritedValueType, navigate, PageView } from '@operato/shell'\nimport { CommonGristStyles, CommonHeaderStyles, ScrollbarStyles } from '@operato/styles'\nimport { isMobileDevice } from '@operato/utils'\nimport { getDomain } from '@things-factory/auth-base/dist-client'\n\nimport type { DataGrist, FetchOption } from '@operato/data-grist'\n\n/**\n * 역할 목록 — **보이는 것과 고칠 수 있는 것을 가른다.**\n *\n * ── 옛 화면이 하던 것 (`pages/role/role-management.ts`) ──────────────────────\n * `quick-find-list` 안에 역할을 늘어놓고, 펼치면 그 안에 이름 입력칸과 84줄의 권한 체크박스가\n * 함께 있었다. 그 구조에서 셋이 났다.\n *\n * ① 이름 입력이 매 글자마다 페이지로 올라가 **모든** 편집기에 공유됐다 — A 를 고치고 B 를\n * 저장하면 B 의 이름이 A 가 됐다. 지금은 이름·설명을 격자에서 고친다.\n * ② 목록에 권한이 하나도 안 보였다. 「이 역할이 무엇을 여나」를 알려면 펼쳐야 했다.\n * ③ 한도도 쪽 나누기도 없었다.\n *\n * ── 상속이 이 화면의 중심이다 ───────────────────────────────────────────────\n * 목록은 부모 도메인의 역할까지 보여 준다 — **부여할 수 있는 범위가 그것**이기 때문이다.\n * 그런데 그 행은 여기서 못 고친다(`updateRole` 이 `domain.id` 하나로 좁혀 찾는다).\n *\n * 전에는 `Include` 로 열어서 **못 고치는 9행이 고칠 수 있는 2행 앞에 깔렸다**(UX 실측).\n * 지금은 「우리 역할」로 열고, 세 묶음의 수를 함께 적어 **상속이 있다는 사실 자체는 남긴다.**\n * 편집도 `editable` 함수로 상속 행에서 닫고, 삭제도 상속 행을 안 보낸다.\n *\n * ── 권한은 수만 낸다 ────────────────────────────────────────────────────────\n * 전에는 `activity 쓰기 · activity-instance 쓰기·읽기` 식으로 잘린 코드 용어를 그렸고, 그것을\n * 그리려고 권한 **행**을 받았다(UX 실측 5,731 bytes · 69ms 대 518 bytes · 7ms). 지금은\n * `privilegeCount` 하나를 받는다. 권한 편집으로 가는 문은 **버튼 gutter 칸**이 진다 —\n * 눌리는 숫자는 손잡이로 보이지 않는다.\n */\nconst RoleListPageBase = p13n(localize(i18next)(PageView))\n\n@customElement('role-list-page')\nexport class RoleListPage extends RoleListPageBase {\n static styles: CSSResultGroup = [\n ScrollbarStyles,\n CommonGristStyles,\n CommonHeaderStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n width: 100%;\n background-color: var(--md-sys-color-surface);\n color: var(--md-sys-color-on-surface);\n position: relative;\n }\n\n ox-grist {\n flex: 1;\n min-height: 0;\n }\n\n /* 요약 배지 — 색은 짝으로, 그리고 「무엇이 나를 기다리나」에만 쓴다. */\n .badge {\n display: inline-block;\n padding: 2px 8px;\n border-radius: var(--md-sys-shape-corner-small, 6px);\n font-size: 0.78rem;\n white-space: nowrap;\n background-color: var(--md-sys-color-surface-variant);\n color: var(--md-sys-color-on-surface-variant);\n }\n .badge[attention] {\n background-color: var(--md-sys-color-error-container);\n color: var(--md-sys-color-on-error-container);\n }\n /* 「우리」는 기본 상태다 — 색을 쓰면 색이 아무것도 말하지 않게 된다. */\n .badge[quiet] {\n background-color: transparent;\n color: var(--md-sys-color-outline);\n }\n\n /*\n ── 세 묶음 ────────────────────────────────────────────────────────────\n 고른 것은 면으로 말한다. 글자색만 바꾸면 야간에 안 보인다.\n */\n [scopes] {\n display: flex;\n align-items: center;\n gap: 4px;\n flex-wrap: wrap;\n }\n\n /*\n 어디에서 물려받나 — 단추가 아니라 **사실을 적은 글**이다. 그래서 알약도 테두리도 없다.\n 고르는 것과 읽는 것이 같은 모양이면 눌러 보게 된다.\n\n 단추 줄에서 한 칸 떨어뜨린다. 붙여 두면 네 번째 단추로 보인다.\n */\n [lineage] {\n margin-left: 8px;\n font-size: 0.78rem;\n color: var(--md-sys-color-on-surface-variant);\n }\n\n button[scope] {\n display: inline-flex;\n align-items: baseline;\n gap: 5px;\n padding: 5px 12px;\n font: var(--label-font, inherit);\n font-size: 0.82rem;\n color: var(--md-sys-color-on-surface-variant);\n background-color: transparent;\n border: 1px solid var(--md-sys-color-outline-variant);\n border-radius: var(--md-sys-shape-corner-full, 999px);\n cursor: pointer;\n white-space: nowrap;\n }\n\n button[scope]:hover {\n background-color: var(--md-sys-color-surface-variant);\n }\n\n button[scope][active] {\n color: var(--md-sys-color-on-secondary-container);\n background-color: var(--md-sys-color-secondary-container);\n border-color: var(--md-sys-color-secondary-container);\n }\n\n button[scope] [count] {\n font-size: 0.78rem;\n color: var(--md-sys-color-outline);\n }\n\n button[scope][active] [count] {\n color: var(--md-sys-color-on-secondary-container);\n }\n\n /*\n * 새 역할 단추 — **옆에 선 것들을 따라간다.**\n *\n * 이 줄에는 알약 범위 단추 셋과 검색이 서 있다. 그 사이에서 이것만 네모난 기본 단추면\n * 같은 줄의 문법이 깨진다. 범위 단추와 같은 모서리·같은 테두리를 쓰되, **여는 일**이므로\n * 채워서 한 단 앞으로 낸다 — 나머지는 거르는 일이고 이것만 만드는 일이다.\n */\n button[add] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 5px 10px;\n color: var(--md-sys-color-on-secondary-container);\n background-color: var(--md-sys-color-secondary-container);\n border: 1px solid transparent;\n border-radius: var(--md-sys-shape-corner-full, 999px);\n cursor: pointer;\n margin-left: auto;\n }\n\n button[add]:hover {\n background-color: var(--md-sys-color-secondary);\n color: var(--md-sys-color-on-secondary);\n }\n\n button[add] md-icon {\n --md-icon-size: 18px;\n }\n\n ox-oops-note[note] {\n position: absolute;\n inset: 0;\n z-index: 5;\n background-color: var(--md-sys-color-surface);\n }\n `\n ]\n\n @property({ type: Object }) config: any\n @property({ type: String }) mode: 'CARD' | 'GRID' | 'LIST' = isMobileDevice() ? 'LIST' : 'GRID'\n\n /* 읽는 중 · 못 읽었다 · 읽었는데 없다 를 가른다. */\n @state() private fetchState: 'loading' | 'failed' | 'loaded' = 'loading'\n @state() private total: number = 0\n @state() private filtered: boolean = false\n\n /*\n * 지금 어느 도메인에 있나.\n *\n * 목록은 상속으로 부모 도메인의 역할까지 보여 준다 — 실제로 부여할 수 있는 범위가 그것이기\n * 때문이다. 그런데 **그 행은 여기서 못 고친다**(`updateRole`·`updateMultipleRole` 이\n * `domain.id` 하나로 좁혀 찾는다). 그래서 편집을 열어 두면 안 된다.\n *\n * 프로필의 `domain` 에는 `id` 가 없어서(`auth-private-process-router.ts`) `subdomain` 으로\n * 견준다 — 두 도메인이 같은 subdomain 을 가질 수 없으므로 id 와 같은 답이다.\n */\n @state() private mySubdomain: string = ''\n\n /**\n * 물려받는 곳. `null` 이면 최상위, `undefined` 면 아직 안 읽었다.\n *\n * 셋을 가르는 이유는 화면이 「모른다」와 「없다」를 다르게 그려야 하기 때문이다.\n */\n @state() private parentDomain: { name?: string; subdomain?: string } | null | undefined = undefined\n\n /*\n * **어느 묶음을 보고 있나 — 기본값은 「우리 역할」이다.**\n *\n * 전에는 `Include` 로 열었다. 그러면 처음 화면에 **못 고치는 행 9개가 앞에 깔린다**\n * (UX 실측: miratek 2 · system 9). `None` 으로 열면 처음 화면이 2행이고 **그 2행이 전부\n * 고칠 수 있는 행**이다.\n *\n * 세 묶음은 서버에 이미 있다(`InheritedValueType`). 새로 만들 것이 없다.\n */\n @state() private scope: InheritedValueType = InheritedValueType.None\n\n /**\n * 묶음마다 몇 건인가.\n *\n * 수를 함께 적는 이유는 **상속이 있다는 사실 자체가 보이게** 하는 것이다. 기본값을 「우리\n * 역할」로 좁혀 놓으면, 수가 없으면 상속 9건이 존재하는지조차 모른다.\n *\n * `undefined` 는 「아직 못 셌다」다 — 0 으로 메우면 「없다」와 같아 보인다.\n */\n @state() private counts: Partial<Record<InheritedValueType, number>> = {}\n\n @query('ox-grist') private grist!: DataGrist\n\n /**\n * 저장과 삭제를 **툴바**에 둔다.\n *\n * 격자에서 고치는 화면들의 관례다(`fiscal-quarter-list-page` 외). 격자 안 편집은 여러 줄을\n * 한꺼번에 고치므로, 줄마다 저장 단추를 두면 어디를 눌러야 하는지가 사라진다.\n */\n /**\n * 찾기는 **제목 줄**에 선다 — 셸이 그 자리를 모든 화면에 같은 모양으로 둔다.\n *\n * 이 페이지는 한동안 `ox-filters-form` 을 헤드룸의 칩 줄에 **직접** 끼워 넣고 있었다. 그래서\n * 돋보기가 범위 칩과 만들기 단추 사이에 끼어, 같은 앱의 다른 목록과 찾기 자리가 달랐다\n * (`env-var-list-page` 외 auth-ui 목록 다섯이 전부 아래 모양이다).\n *\n * 둘은 대안이 아니라 **다른 것**이다 — `search` 는 목록 전체를 훑는 글자 찾기이고,\n * `filter` 는 칸별 조건 폼을 접었다 펴는 것이다. 표준 화면은 둘 다 선언한다.\n */\n get context() {\n return {\n title: i18next.t('text.role_management'),\n help: 'auth/roles',\n search: {\n handler: (search: string) => {\n this.grist.searchText = search\n },\n value: this.grist?.searchText || ''\n },\n filter: {\n handler: () => {\n this.grist.toggleHeadroom()\n }\n },\n actions: [\n { title: i18next.t('button.save'), action: this.onSaveGrid.bind(this) },\n { title: i18next.t('button.delete'), action: this.onDeleteGrid.bind(this) }\n ]\n }\n }\n\n render() {\n return html`\n <ox-grist\n .mode=${this.mode}\n .config=${this.config}\n .fetchHandler=${this.fetchHandler.bind(this)}\n .personalConfigProvider=${this.getPagePreferenceProvider('ox-grist')!}\n >\n <div slot=\"headroom\" class=\"header\">\n <div class=\"filters\">\n ${this.renderScopes()}\n\n <!-- 찾기는 제목 줄이 갖는다(context.search). 폼이 자기 검색칸을 또 그리면 둘이 된다. -->\n <ox-filters-form without-search></ox-filters-form>\n\n <!--\n 여기 ox-record-creator 가 있었다 — 목록에 빈 줄을 만들고 이름을 치게 하는\n 관례(24 파일)다. 걷어낸 이유는 openCreate 의 머리말에 있다.\n -->\n <button add @click=${() => this.openCreate()} title=${i18next.t('button.create')}>\n <md-icon>add</md-icon>\n </button>\n </div>\n </div>\n\n <ox-grist-personalizer slot=\"setting\"></ox-grist-personalizer>\n </ox-grist>\n\n ${this.renderNote()}\n `\n }\n\n /**\n * 세 묶음.\n *\n * **수를 함께 적는 것이 이 부품의 이유다.** 기본값을 「우리 역할」로 좁혀 두면 처음 화면이\n * 깨끗한 대신 **상속 9건이 있다는 사실이 사라진다.** 수가 그것을 되돌려 놓는다.\n *\n * 아직 못 센 묶음은 수를 **안 그린다** — 0 으로 메우면 「없다」와 같아 보인다.\n */\n private renderScopes() {\n const scopes: [InheritedValueType, string][] = [\n [InheritedValueType.None, 'label.ours'],\n [InheritedValueType.Only, 'label.inherited'],\n [InheritedValueType.Include, 'label.all']\n ]\n\n return html`\n <div scopes>\n ${scopes.map(\n ([scope, key]) => html`\n <button scope ?active=${this.scope === scope} @click=${() => this.onScope(scope)}>\n ${i18next.t(key)}\n ${this.counts[scope] === undefined ? nothing : html`<span count>${this.counts[scope]}</span>`}\n </button>\n `\n )}\n ${this.renderLineage()}\n </div>\n `\n }\n\n /**\n * 어디에서 물려받나 — **있을 때나 없을 때나 같은 자리에서 말한다.**\n *\n * 「상속」 단추만 두면 눌러 보기 전에는 그것이 무엇을 뜻하는지 알 수 없다. 부모가 있는\n * 도메인에서는 **어느 도메인인지**가 필요하고(그 역할을 고치러 가야 할 곳이다), 최상위\n * 도메인에서는 **물려받는 곳이 없다는 사실**이 필요하다. 후자를 안 적으면 「상속 0」이\n * 「아직 안 만들었나」로 읽힌다.\n *\n * 단추를 숨기지 않는다. 숨기면 최상위 도메인의 관리자는 상속이라는 것이 있다는 사실\n * 자체를 모르고, 자식 도메인을 만든 뒤에 갑자기 나타나는 단추를 만난다.\n *\n * 아직 프로필을 못 읽었으면 **아무것도 안 적는다** — 모르는 것을 「없다」로 적지 않는다.\n */\n private renderLineage() {\n if (this.parentDomain === undefined) {\n return nothing\n }\n\n const from = this.parentDomain?.name || this.parentDomain?.subdomain\n\n return html`\n <span lineage>\n ${from\n ? i18next.t('text.inherited from x', { x: from })\n : i18next.t('text.this domain is top level and inherits from nowhere')}\n </span>\n `\n }\n\n private onScope(scope: InheritedValueType) {\n if (this.scope === scope) {\n return\n }\n\n this.scope = scope\n this.grist.fetch()\n }\n\n /**\n * 세 묶음의 건수.\n *\n * 세 번 묻는다. 한 번에 셋을 세는 질의가 서버에 없고, 각각 `total` 만 받으므로 가볍다\n * (UX 실측 158·566·684 bytes). 실패하면 **수를 안 그린다** — 틀린 수를 그리는 것보다\n * 없는 것이 낫다.\n */\n private async countScopes() {\n const scopes = [InheritedValueType.None, InheritedValueType.Only, InheritedValueType.Include]\n\n const counted = await Promise.all(\n scopes.map(async scope => {\n const response = await client.query({\n query: gql`\n query ($inherited: InheritedValueType) {\n roles(inherited: $inherited, pagination: { page: 1, limit: 1 }) {\n total\n }\n }\n `,\n variables: { inherited: scope }\n })\n\n return [scope, response.errors?.length ? undefined : response.data?.roles?.total] as const\n })\n )\n\n this.counts = counted.reduce((counts, [scope, total]) => {\n if (total !== undefined) {\n counts[scope] = total\n }\n return counts\n }, {} as Partial<Record<InheritedValueType, number>>)\n }\n\n /**\n * 목록 위에 겹쳐 그린다 — 격자를 떼면 다시 읽을 길이 없어진다.\n *\n * **아무 역할도 없는 것이 이 화면의 가장 중요한 자리다.** 새 도메인은 여기서 시작하고,\n * 역할이 없으면 초대한 사람은 로그인해도 볼 화면이 하나도 없다. 그래서 그 사실을 말한다.\n */\n private renderNote() {\n if (this.fetchState === 'loading') {\n return nothing\n }\n\n if (this.fetchState === 'failed') {\n return html`<ox-oops-note\n note\n icon=\"error_outline\"\n title=${i18next.t('text.failed to fetch')}\n description=${i18next.t('text.please try again')}\n ></ox-oops-note>`\n }\n\n if (this.total === 0 && !this.filtered) {\n return html`<ox-oops-note\n note\n icon=\"badge\"\n title=${i18next.t('text.no role yet')}\n description=${i18next.t('text.without a role they see nothing')}\n ></ox-oops-note>`\n }\n\n return nothing\n }\n\n /**\n * 상속된 역할인가 — **격자의 편집 가드가 이것을 본다.**\n *\n * `mySubdomain` 을 아직 못 읽었으면 **거짓을 돌려주지 않고 상속으로 본다.** 「모른다」를\n * 「내 것이다」로 바꾸면 그 순간 못 고치는 행이 열리고, 사람이 타이핑해 저장한 뒤에 서버가\n * 거절한다. 모를 때는 잠가 두는 쪽이 안전하다.\n */\n private isInherited(record: any): boolean {\n if (!this.mySubdomain) {\n return true\n }\n\n const subdomain = record?.domain?.subdomain\n return Boolean(subdomain) && subdomain !== this.mySubdomain\n }\n\n /**\n * 출처 한 칸.\n *\n * 「우리」는 조용히 둔다 — 기본 상태에 색을 쓰면 색이 아무것도 말하지 않게 된다.\n * 상속은 **어느 도메인에서 왔는지 함께 적는다.** 「상속」만 적으면 관리자가 어디로 가야\n * 하는지 모른다.\n */\n private sourceCell(record: any) {\n if (!this.isInherited(record)) {\n return html`<span class=\"badge\" quiet>${i18next.t('label.ours')}</span>`\n }\n\n const from = record?.domain?.name || record?.domain?.subdomain || ''\n return html`<span class=\"badge\" title=${i18next.t('text.role belongs to x domain and is edited there', { x: from })}\n >${i18next.t('label.inherited')}${from ? ` · ${from}` : ''}</span\n >`\n }\n\n async pageInitialized() {\n const mine: any = await getDomain()\n\n this.mySubdomain = mine?.subdomain || ''\n\n /*\n * `undefined` 는 「아직 모른다」, `null` 은 「부모가 없다」다. 프로필이 `parent` 칸을\n * 들고 오므로 둘이 갈린다 — 옛 서버가 그 칸을 안 보내면 `undefined` 로 남아 아무것도\n * 안 적는다. 「모른다」를 「없다」로 접지 않는다.\n */\n this.parentDomain = mine && 'parent' in mine ? mine.parent : undefined\n\n this.countScopes()\n\n this.config = {\n /*\n * **두 모드가 다른 말을 하지 않게 한다.**\n *\n * ⚠ 여기가 `summary`·`domain` 을 가리킨 채로 남아 있었다 — 컬럼을 `privilegeCount`·\n * `source` 로 갈았는데 이 설정을 안 고쳤다. 그래서 **LIST 모드에서는 권한 수도 출처도\n * 아예 안 그려졌다.** 이름이 틀려도 격자가 조용히 넘어가므로 빌드도 시험도 못 잡는다.\n *\n * 그리고 이 앱이 실제로 LIST 로 연다(`isMobileDevice()` 또는 사람마다 기억된 개인 설정).\n * GRID 만 보고 만들면 그 사람들은 내가 만든 칸을 한 번도 못 본다.\n */\n list: {\n fields: ['name', 'description'],\n details: ['privilegeCount', 'source']\n },\n columns: [\n { type: 'gutter', gutterName: 'sequence', fixed: true },\n {\n /*\n * **권한 편집으로 가는 문.**\n *\n * `gutterName: 'button'` 이 이 저장소의 관례다 — 68 파일 · 104 자리\n * (`employee-list-page.ts:110-124` 가 같은 모양). `header` 에 이름이 붙은 아이콘 칸은\n * **손잡이로 보인다.** 그것이 「권한 편집 방법을 모르겠다」를 고치는 자리다.\n *\n * 행 전체에 클릭을 두지 않는 이유는 그대로다 — 이름을 고치려 누른 것이 화면을\n * 떠나면 안 된다.\n *\n * ── 상속 행에서도 버튼은 남긴다 ─────────────────────────────────────\n * **읽는 것은 정당하다.** 상속 역할이 무엇을 여는지 보는 것은 막을 이유가 없고,\n * 오히려 「우리 역할로 복사」를 하려면 먼저 봐야 한다. `icon`·`title` 이 record 를\n * 받으므로 그 구분이 여기 실린다 — 글자만 「보기만」으로 바뀐다.\n */\n type: 'gutter',\n gutterName: 'button',\n header: i18next.t('field.privileges'),\n /*\n * ⚠ **아직 저장 안 된 빈 줄에는 내주지 않는다.** 격자가 데이터 2행 아래에 추가용 빈\n * 줄을 하나 더 그리는데, 거기에도 버튼이 나왔다. 눌러도 `record.id` 가 없어 아무 일이\n * 없다 — **조용한 무동작 단추**다.\n *\n * `employee-list-page` 가 `icon` 에서 `''` 를 돌려주는 이유가 이것이었다. 모양만\n * 베끼고 그 가드를 안 가져왔다.\n */\n icon: (record: any) => (record?.id ? 'key' : ''),\n iconOnly: false,\n title: (record: any) =>\n !record?.id\n ? ''\n : this.isInherited(record)\n ? i18next.t('text.view only')\n : i18next.t('button.edit privileges'),\n width: 92,\n handlers: {\n click: (_columns: any, _data: any, _column: any, record: any) => {\n if (record?.id) {\n navigate(`role-privileges/${record.id}`)\n }\n }\n }\n },\n {\n type: 'string',\n name: 'name',\n header: i18next.t('field.name'),\n /*\n * **격자 안에서 고친다.** 이 저장소의 관례가 그렇고(`editable` 139 파일 대\n * `openPopup` 96 파일), 옛 화면이 이름을 팝업에 두었다가 A 의 이름을 B 에 저장했다.\n *\n * ⚠ **상속된 행은 열지 않는다.** `editable` 이 함수를 받는다\n * (`data-grist` types.ts:458, data-grid-body.js:530·861 이 그렇게 부른다). 이것이\n * 없으면 부모 도메인 역할에 타이핑해 저장할 수 있고, 서버가 그때 거절한다.\n */\n record: { align: 'left', editable: (_v: any, _c: any, record: any) => !this.isInherited(record) },\n filter: 'search',\n sortable: true,\n width: 160\n },\n {\n type: 'string',\n name: 'description',\n header: i18next.t('field.description'),\n record: { align: 'left', editable: (_v: any, _c: any, record: any) => !this.isInherited(record) },\n filter: 'search',\n sortable: true,\n width: 220\n },\n {\n /*\n * **이 역할이 여는 것이 몇 개인가.** 읽는 값이고, 누르는 자리가 아니다.\n *\n * 전에는 여기에 `activity 쓰기 · activity-instance 쓰기·읽기 · 3개 더` 식으로 잘린\n * 코드 용어를 그렸다. 그것을 그리려고 목록이 권한 **행**을 받았다.\n *\n * ```\n * [UX 실측] roles(limit:20) 권한 포함 5,731 bytes · 69ms\n * [UX 실측] 권한 없이 518 bytes · 7ms → 11.1배 · 10배\n * ```\n *\n * 수는 잘린 문자열이 못 하던 것을 한다 — 「더 있다」가 아니라 **얼마인지** 말하고,\n * `0` 이면 아직 아무것도 안 열린 역할이라는 것이 한눈에 보인다.\n *\n * ⚠ **여기에 클릭을 얹지 않는다.** 한때 그렇게 뒀는데, 읽는 값과 누르는 자리가 한 칸에\n * 들어간다. 그리고 사용자 평가의 첫 줄이 「권한 편집 방법을 모르겠다」였는데 —\n * **눌리는 숫자는 보이는 손잡이가 아니다.** 문은 아래 버튼 칸이 진다.\n */\n type: 'string',\n name: 'privilegeCount',\n header: i18next.t('field.privileges'),\n record: { align: 'center', renderer: privilegeCountCell },\n width: 110\n },\n {\n /*\n * **출처 — 여기가 「보이는 것과 고칠 수 있는 것」을 가른다.**\n *\n * 전에는 도메인 이름을 날값으로 냈다(`SYSTEM` · `MIRATEK`). 그건 구분이 아니다 —\n * 읽는 사람이 자기 도메인 이름을 외우고 있어야 한다. 지금은 「우리」와\n * 「상속 · SYSTEM」 으로 말한다.\n *\n * ⚠ `classifier` 로 행 전체를 물들이지 않는다. 선례들이 색을 `'#8B0000'` 처럼 박아\n * 두었고(dataset 5곳), 그 규약(`emphasized: [배경, 글자]`)이 `var(...)` 를 받는지도\n * 확인되지 않았다. 그리고 `emphasized` 는 **강조**하는 장치인데 여기서 필요한 것은\n * 그 반대다 — 11행 중 9행을 강조하면 강조가 아니다. 그래서 이 칸이 진다.\n */\n type: 'string',\n name: 'source',\n header: i18next.t('field.source'),\n record: { align: 'center', renderer: (_v: any, _c: any, record: any) => this.sourceCell(record) },\n width: 150\n },\n {\n type: 'datetime',\n name: 'updatedAt',\n header: i18next.t('field.updated_at'),\n record: { align: 'center' },\n sortable: true,\n width: 160\n }\n ],\n rows: {\n selectable: { multiple: true }\n },\n sorters: [{ name: 'name' }]\n }\n }\n\n async pageUpdated(_changes: any, _lifecycle: any) {\n if (this.active) {\n await this.updateComplete\n this.grist.fetch()\n }\n }\n\n /**\n * 역할을 가져온다 — **권한 행은 받지 않는다.**\n *\n * 전에는 `privileges { id name category }` 를 함께 물었다. 그것으로 그린 것은 잘린 코드 용어\n * 셋이었고, 값은 이랬다.\n *\n * ```\n * [UX 실측] 권한 포함 5,731 bytes · 69ms\n * [UX 실측] 권한 없이 518 bytes · 7ms\n * ```\n *\n * 바이트보다 큰 것은 **`Role.privileges` 필드 리졸버가 역할마다 한 번씩 도는 것**이 함께\n * 없어지는 쪽이다. 지금은 `privilegeCount` 를 받는다 — DB 가 센다(`role-query.ts`).\n *\n * `inherited` 는 화면이 고른 묶음을 그대로 넘긴다. 기본값이 「우리 역할」이라 처음 화면에\n * 못 고치는 행이 안 깔린다.\n */\n async fetchHandler({ filters = [], page, limit, sortings = [] }: FetchOption) {\n this.fetchState = 'loading'\n this.filtered = filters.length > 0\n\n try {\n const response = (\n await client.query({\n query: gql`\n query (\n $filters: [Filter!]\n $pagination: Pagination\n $sortings: [Sorting!]\n $inherited: InheritedValueType\n ) {\n responses: roles(\n filters: $filters\n pagination: $pagination\n sortings: $sortings\n inherited: $inherited\n ) {\n items {\n id\n name\n description\n domain {\n id\n name\n subdomain\n }\n privilegeCount\n updatedAt\n }\n total\n }\n }\n `,\n variables: {\n filters,\n pagination: { page, limit },\n sortings: sortings.length ? sortings : [{ name: 'name' }],\n inherited: this.scope\n }\n })\n ).data.responses\n\n this.total = response.total\n this.fetchState = 'loaded'\n\n return {\n records: response.items,\n total: response.total\n }\n } catch (e) {\n /* 못 읽은 것을 「없다」로 그리지 않는다. */\n this.fetchState = 'failed'\n throw e\n }\n }\n\n /**\n * 격자에서 고친 줄들을 저장한다.\n *\n * 이 저장소의 목록 관례를 그대로 쓴다 — `dirtyRecords` 를 `__dirtyfields__[key].after` 로 펴서\n * `cuFlag` 와 함께 보낸다(`fiscal-quarter-list-page` 가 같은 모양). 서버가\n * `updateMultipleRole` 로 받는다.\n *\n * **`privileges` 는 안 보낸다.** 여기서 고치는 것은 이름과 설명이고, 권한은 전용 화면이\n * 소유한다. 안 보낸 것을 「비우라」로 읽으면 이름만 고친 순간 그 역할이 아무것도 안 여는\n * 역할이 된다 — 서버 쪽에도 같은 말을 주석으로 적어 뒀다.\n */\n private async onSaveGrid() {\n const dirty = this.grist.dirtyRecords\n if (!dirty?.length) {\n return\n }\n\n const patches = dirty.map((record: any) => {\n const patch: any = record.id ? { id: record.id } : {}\n for (const key in record.__dirtyfields__) {\n patch[key] = record.__dirtyfields__[key].after\n }\n patch.cuFlag = record.__dirty__\n return patch\n })\n\n const response = await client.mutate({\n mutation: gql`\n mutation ($patches: [RolePatch!]!) {\n updateMultipleRole(patches: $patches) {\n id\n }\n }\n `,\n variables: { patches },\n context: gqlContext()\n })\n\n if (response.errors?.length) {\n /* 실패를 조용히 넘기지 않는다 — 격자는 고친 채로 남아 있어야 다시 저장할 수 있다. */\n document.dispatchEvent(\n new CustomEvent('notify', { detail: { level: 'error', message: response.errors[0]?.message } })\n )\n return\n }\n\n this.grist.fetch()\n }\n\n /**\n * 고른 역할을 지운다.\n *\n * 서버는 **가진 사람이 있는 역할을 남긴다**(`deleteRoles`). 그래서 여기서 「지웠다」고 말하지\n * 않고 목록을 다시 읽는다 — 무엇이 남았는지가 화면에서 보이는 것이 맞다.\n */\n private async onDeleteGrid() {\n const selected = this.grist.selected\n if (!selected?.length) {\n return\n }\n\n /*\n * **상속된 행은 여기서 못 지운다** — `deleteRoles` 가 `domain.id` 로 좁혀 찾으므로 그 행은\n * 조용히 남는다. 「지웠다」로 보이고 목록에 그대로 있으면 사람이 두 번 세 번 누른다.\n */\n const mine = selected.filter((record: any) => !this.isInherited(record))\n if (!mine.length) {\n await OxPrompt.open({\n type: 'warning',\n title: i18next.t('button.delete'),\n text: i18next.t('text.inherited roles are edited in their own domain'),\n confirmButton: { text: i18next.t('button.confirm') }\n })\n return\n }\n\n if (\n !(await OxPrompt.open({\n type: 'warning',\n title: i18next.t('button.delete'),\n text: i18next.t('text.roles in use are kept'),\n confirmButton: { text: i18next.t('button.confirm') },\n cancelButton: { text: i18next.t('button.cancel') }\n }))\n ) {\n return\n }\n\n const response = await client.mutate({\n mutation: gql`\n mutation ($ids: [String!]!) {\n deleteRoles(ids: $ids)\n }\n `,\n variables: { ids: mine.map((record: any) => record.id) },\n context: gqlContext()\n })\n\n if (response.errors?.length) {\n document.dispatchEvent(\n new CustomEvent('notify', { detail: { level: 'error', message: response.errors[0]?.message } })\n )\n return\n }\n\n this.grist.fetch()\n }\n\n /**\n * 역할을 만드는 두 문을 **한 자리에** 연다.\n *\n * ── 왜 `ox-record-creator`(빈 줄)를 걷어냈나 ────────────────────────────────\n * 이름을 짓는 것은 쉬운 쪽이고 「품질검사원에게 84개 중 무엇을 주나」가 어려운 쪽인데, 빈\n * 줄은 그 판단을 통째로 사람에게 넘긴다. 미라텍이 그래서 역할 둘뿐이다.\n *\n * 그리고 그 단추는 `.filters` 안에 있었는데 `CommonHeaderStyles` 는 `.actions button` 만\n * 꾸민다. `.filters` 에 button 규칙이 없어서 **브라우저 기본 단추**가 그대로 나왔다 — 옆의\n * 알약 범위 단추·검색과 같은 줄에서 혼자 네모난 회색 상자였다. 이 관례를 쓰는 다른 목록\n * 화면(24 파일)도 전부 같은 상태다.\n *\n * ── 순서가 가르친다 ────────────────────────────────────────────────────────\n * 템플릿이 먼저고 백지가 나중이다. 「품질검사원, 권한 7」을 본 사람은 자리가 어떻게 생긴\n * 것인지 알고 나서 자기 것을 짓는다. 그렇다고 백지를 감추면 안 된다 — 「야간반장」·「외주\n * 검사」 같은 자리는 그 공장이 아는 것이지 제품이 아는 게 아니다.\n */\n private async openCreate() {\n await import('../../components/role-template-picker.js')\n\n const popup = openPopup(\n html`<role-template-picker .page=${this} @close=${() => popup.close()}></role-template-picker>`,\n {\n backdrop: true,\n size: 'large',\n title: i18next.t('button.create role')\n }\n )\n }\n\n /** 템플릿에서 — 이름은 이 공장이 정한 것을 그대로 보낸다. */\n async createFromTemplate(templateId: string, name: string) {\n const response = await client.mutate({\n mutation: gql`\n mutation ($templateId: String!, $name: String) {\n createRoleFromTemplate(templateId: $templateId, name: $name) {\n id\n }\n }\n `,\n variables: { templateId, name },\n context: gqlContext()\n })\n\n if (response.errors?.length) {\n document.dispatchEvent(\n new CustomEvent('notify', { detail: { level: 'error', message: response.errors[0]?.message } })\n )\n return false\n }\n\n this.grist.fetch()\n return true\n }\n\n /** 백지에서 — 권한 없이 선다. 목록의 권한 수 칸이 0 을 빨갛게 말하므로 다음 걸음이 보인다. */\n async createBlankRole(name: string, description: string) {\n return await this.onCreateRole({ name, description })\n }\n\n /**\n * 새 역할.\n *\n * **권한 없이 만들어진다.** 그것이 맞다 — 이름을 정하는 것과 무엇을 열지 정하는 것은 다른\n * 판단이고, 목록의 요약 칸이 「여는 것이 없음」을 빨갛게 말하므로 다음 걸음이 보인다.\n */\n private async onCreateRole(record: any) {\n const response = await client.mutate({\n mutation: gql`\n mutation ($role: NewRole!) {\n createRole(role: $role) {\n id\n }\n }\n `,\n variables: { role: { name: record.name, description: record.description } },\n context: gqlContext()\n })\n\n if (response.errors?.length) {\n document.dispatchEvent(\n new CustomEvent('notify', { detail: { level: 'error', message: response.errors[0]?.message } })\n )\n return false\n }\n\n this.grist.fetch()\n return true\n }\n}\n\n/**\n * 권한 수 한 칸 — **0 이 가장 할 말이 많은 값이다.**\n *\n * 권한이 하나도 없는 역할은 붙여도 아무 일이 없다. 그것이 이 화면에서 **조치가 필요한 유일한\n * 상태**이므로 색을 그때만 쓴다. 나머지는 수만 낸다.\n */\nfunction privilegeCountCell(_value: any, _column: any, record: any) {\n const count = record?.privilegeCount\n\n /* 「모른다」와 「0」은 다르다 — 아직 안 온 값에 색을 쓰지 않는다. */\n if (count === undefined || count === null) {\n return html``\n }\n\n return count === 0\n ? html`<span class=\"badge\" attention>${i18next.t('text.no privilege granted')}</span>`\n : html`<span class=\"badge\">${count}</span>`\n}\n"]}
|
|
@@ -166,7 +166,6 @@ let UserListPage = class UserListPage extends UserListPageBase {
|
|
|
166
166
|
<div class="filters">
|
|
167
167
|
<ox-filters-form autofocus></ox-filters-form>
|
|
168
168
|
</div>
|
|
169
|
-
<ox-context-page-toolbar class="actions" .context=${this.context}></ox-context-page-toolbar>
|
|
170
169
|
</div>
|
|
171
170
|
|
|
172
171
|
<ox-grist-personalizer slot="setting"></ox-grist-personalizer>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-list-page.js","sourceRoot":"","sources":["../../../client/pages/user/user-list-page.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@operato/data-grist'\nimport '@operato/oops/ox-oops-note.js'\nimport '../../components/user-detail.js'\nimport '../../components/user-invite.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, nothing } from 'lit'\nimport type { CSSResultGroup } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\n\nimport { client } from '@operato/graphql'\nimport { openPopup } from '@operato/layout'\nimport { i18next, localize } from '@operato/i18n'\nimport { p13n } from '@operato/p13n'\nimport { PageView } from '@operato/shell'\nimport { CommonGristStyles, CommonHeaderStyles, ScrollbarStyles } from '@operato/styles'\nimport { isMobileDevice } from '@operato/utils'\n\nimport type { DataGrist, FetchOption } from '@operato/data-grist'\n\n/**\n * 사용자 목록 — **서버가 거르고, 서버가 쪽을 나눈다.**\n *\n * ── 옛 화면이 하던 것 (`pages/user/user-management.ts`) ──────────────────────\n * `query { users { items { … } } }` 로 **도메인의 모든 사용자**를 한 번에 가져왔다. 한도도\n * 서버 검색도 없었고, 프레임워크가 이미 그것을 경고하고 있었다\n * (`unbounded list query on \"User\"`). 사용자는 권한(84행)과 달리 실제로 늘어난다.\n *\n * 그런데 **서버는 처음부터 받을 준비가 되어 있었다** — `users(filters:, pagination:, sortings:)`\n * 가 `ListParam` 을 받고 `name`·`email`·`description` 을 검색어로 본다\n * (`user-query.ts`). 화면이 안 쓰고 있었을 뿐이다.\n *\n * ── 왜 `ox-grist` 인가 ──────────────────────────────────────────────────────\n * 손으로 만든 목록이 잃는 것을 그것이 이미 갖고 있다 — 쪽 나누기, 정렬, 컬럼 걸러 보기,\n * 사람마다 기억하는 개인 설정, 카드↔표 모드, 내보내기. `figure-list-page` 가 같은 이유로\n * 옮겼고 그 판단을 그대로 따른다.\n *\n * ── 옛 화면의 탭을 없앤 이유 ────────────────────────────────────────────────\n * 탭이 `userType` 값으로 갈렸다. 그런데 그 칸은 `varchar` 이고 코드가 예상하는 넷\n * (`admin`·`user`·`application`·`appliance`) 밖의 값이거나 비어 있으면 **그 사람이 어느 탭에도\n * 안 나온다 — 화면에서 조용히 사라진다.**\n *\n * 여기서는 사람만 싣는다. 애플리케이션과 어플라이언스는 자기 화면(`applications`·`appliance`)에\n * 있고, 거기서 자기 자격증명까지 함께 다루는 것이 맞다. 다만 **거르는 것은 서버가 해야 한다** —\n * 지금은 `users` 가 종류를 안 가리므로 이 화면이 filter 로 요구한다.\n */\n/*\n * `as typeof PageView` 로 조이지 않는다 — 그렇게 하면 p13n 이 얹어 주는\n * `getPagePreferenceProvider` 가 타입에서 사라져 개인 설정(모드·정렬·쪽 크기를 사람마다\n * 기억하는 것)을 붙일 수 없다. `figure-list-page` 가 같은 주석을 달고 있다.\n */\nconst UserListPageBase = p13n(localize(i18next)(PageView))\n\n@customElement('user-list-page')\nexport class UserListPage extends UserListPageBase {\n static styles: CSSResultGroup = [\n ScrollbarStyles,\n CommonGristStyles,\n CommonHeaderStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n width: 100%;\n background-color: var(--md-sys-color-surface);\n color: var(--md-sys-color-on-surface);\n /* 떠 있는 단추의 기준 상자 */\n position: relative;\n }\n\n ox-grist {\n flex: 1;\n min-height: 0;\n }\n\n /*\n ── 상태 배지 ──────────────────────────────────────────────────────────\n 색은 **짝으로** 쓴다(container / on-container). 한쪽만 쓰면 야간에 대비가 깨진다.\n 그리고 색은 「무엇이 나를 기다리나」에만 쓴다 — 활성은 기본값이라 색이 없다.\n 담는 면이 행이므로 모서리는 작게 잡는다.\n */\n .badge {\n display: inline-block;\n padding: 2px 8px;\n border-radius: var(--md-sys-shape-corner-small, 6px);\n font-size: 0.78rem;\n white-space: nowrap;\n background-color: var(--md-sys-color-surface-variant);\n color: var(--md-sys-color-on-surface-variant);\n }\n .badge[attention] {\n background-color: var(--md-sys-color-error-container);\n color: var(--md-sys-color-on-error-container);\n }\n .badge[quiet] {\n color: var(--md-sys-color-outline);\n }\n\n /* 격자를 떼지 않고 위에 겹친다 — 격자가 살아 있어야 다시 읽을 수 있다. */\n ox-oops-note[note] {\n position: absolute;\n inset: 0;\n z-index: 5;\n background-color: var(--md-sys-color-surface);\n }\n\n /*\n ── 사람 부르기 ────────────────────────────────────────────────────────\n 이 화면의 주된 동작 하나. 글자를 함께 둔다 — 더하기 표만으로는 무엇을 부르는지 모른다.\n 떠 있는 것의 모서리는 알약이다(담는 면을 따르지 않는다).\n */\n button[fab] {\n position: absolute;\n right: var(--spacing-large, 12px);\n bottom: var(--spacing-large, 12px);\n z-index: 10;\n\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 10px 18px 10px 14px;\n font: var(--label-font, inherit);\n font-size: 0.82rem;\n font-weight: 600;\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border: none;\n border-radius: var(--md-sys-shape-corner-full, 999px);\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.18);\n cursor: pointer;\n }\n button[fab]:hover {\n color: var(--md-sys-color-on-primary, #fff);\n background-color: var(--md-sys-color-primary);\n }\n button[fab] md-icon {\n --md-icon-size: 20px;\n }\n `\n ]\n\n @property({ type: Object }) config: any\n /*\n * 카드가 아니라 **행**이다. 카드는 그림으로 알아보는 물건(형상 카탈로그)의 모양이고,\n * 사람은 이름으로 알아본다. 좁은 화면에서는 `LIST` 가 같은 행을 접어 그린다.\n */\n @property({ type: String }) mode: 'CARD' | 'GRID' | 'LIST' = isMobileDevice() ? 'LIST' : 'GRID'\n\n @state() private lockDurationMinutes: number = 10\n\n /*\n * **읽는 중 · 못 읽었다 · 읽었는데 없다 를 가른다.** 셋을 한 문장으로 그리면 못 읽은 것을\n * 「없다」로 읽고, 사용자가 이미 있는 것을 만들러 간다.\n */\n @state() private fetchState: 'loading' | 'failed' | 'loaded' = 'loading'\n @state() private total: number = 0\n @state() private filtered: boolean = false\n\n @query('ox-grist') private grist!: DataGrist\n\n get context() {\n return {\n title: i18next.t('text.user management'),\n help: 'auth/users'\n }\n }\n\n render() {\n return html`\n <ox-grist\n .mode=${this.mode}\n .config=${this.config}\n .fetchHandler=${this.fetchHandler.bind(this)}\n .personalConfigProvider=${this.getPagePreferenceProvider('ox-grist')!}\n >\n <div slot=\"headroom\" class=\"header\">\n <div class=\"filters\">\n <ox-filters-form autofocus></ox-filters-form>\n </div>\n <ox-context-page-toolbar class=\"actions\" .context=${this.context}></ox-context-page-toolbar>\n </div>\n\n <ox-grist-personalizer slot=\"setting\"></ox-grist-personalizer>\n </ox-grist>\n\n ${this.renderNote()}\n\n <button fab @click=${() => this.onInvite()}>\n <md-icon>person_add</md-icon>${i18next.t('label.add user')}\n </button>\n `\n }\n\n /**\n * 목록 위에 겹쳐 그린다 — 격자를 떼면 다시 읽을 길이 없어진다.\n *\n * 거른 결과가 비어 있는 것은 **가르칠 자리가 아니다.** 그때는 격자가 스스로 「없음」을\n * 말하게 두고, 여기서는 아무것도 그리지 않는다.\n */\n private renderNote() {\n if (this.fetchState === 'loading') {\n return nothing\n }\n\n if (this.fetchState === 'failed') {\n return html`<ox-oops-note\n note\n icon=\"error_outline\"\n title=${i18next.t('text.failed to fetch')}\n description=${i18next.t('text.please try again')}\n ></ox-oops-note>`\n }\n\n if (this.total === 0 && !this.filtered) {\n /* 새 도메인의 첫 화면. 소유자는 역할 없이도 모두 열리므로 그 사실부터 말한다. */\n return html`<ox-oops-note\n note\n icon=\"group_add\"\n title=${i18next.t('text.no other user yet')}\n description=${i18next.t('text.owner opens everything without a role')}\n ></ox-oops-note>`\n }\n\n return nothing\n }\n\n async pageInitialized() {\n this.config = {\n /*\n * 카드에 실을 것. 표 모드도 같은 컬럼 설정을 쓰므로 **두 모드가 다른 말을 하지 않는다.**\n */\n list: {\n fields: ['name', 'email'],\n details: ['status', 'roles']\n },\n columns: [\n { type: 'gutter', gutterName: 'sequence', fixed: true },\n {\n type: 'string',\n name: 'name',\n header: i18next.t('field.name'),\n record: { align: 'left', renderer: nameWithOwnerMark },\n filter: 'search',\n sortable: true,\n width: 160\n },\n {\n type: 'string',\n name: 'email',\n header: i18next.t('field.email'),\n record: { align: 'left' },\n filter: 'search',\n sortable: true,\n width: 220\n },\n {\n /*\n * 상태 하나로 두 가지를 말한다 — 지금 상태와, 잠긴 경우 **언제 풀리는지**.\n * `lockedAt` 과 설정된 잠금 시간으로 계산한다. 옛 화면은 「잠김」까지만 말했고,\n * 그래서 관리자가 기다리면 되는지 풀어 줘야 하는지 알 수 없었다.\n */\n type: 'string',\n name: 'status',\n header: i18next.t('field.status'),\n record: { align: 'center', renderer: statusRenderer(() => this.lockDurationMinutes) },\n filter: {\n options: ['', 'activated', 'locked', 'inactive'],\n multiple: false,\n operator: 'eq'\n },\n sortable: true,\n width: 140\n },\n {\n /*\n * 역할이 비어 있는 것을 **눈에 띄게** 그린다. 역할 없는 활성 계정은 로그인은 되는데\n * 화면이 하나도 없다 — 옛 화면에서는 그것이 빈 칸이라 아무도 알아채지 못했다.\n */\n type: 'string',\n name: 'roles',\n header: i18next.t('field.roles'),\n record: { align: 'left', renderer: rolesRenderer },\n width: 260\n },\n {\n type: 'datetime',\n name: 'updatedAt',\n header: i18next.t('field.updated_at'),\n record: { align: 'center' },\n sortable: true,\n width: 160\n }\n ],\n rows: {\n selectable: { multiple: false },\n handlers: {\n click: (_columns: any, _data: any, _column: any, record: any) => this.onSelectUser(record)\n }\n },\n sorters: [{ name: 'name' }]\n }\n }\n\n async pageUpdated(changes: any, lifecycle: any) {\n if (this.active) {\n await this.updateComplete\n this.grist.fetch()\n }\n }\n\n /**\n * 사람만 가져온다. **거르는 것은 서버가 한다** — 옛 화면은 전부 받아 클라이언트에서 나눴다.\n *\n * `userType` 은 `varchar` 라 값이 넷 밖으로 나갈 수 있다. 그래서 「기계가 아닌 것」을\n * `not-in` 으로 요구한다 — 「사람인 것」을 `in` 으로 요구하면 값이 비어 있는 사람이 사라진다.\n * (`userType` 을 enum 으로 못 박는 것은 §3 의 일이다.)\n */\n async fetchHandler({ filters = [], page, limit, sortings = [] }: FetchOption) {\n this.fetchState = 'loading'\n /* 사람만 보려고 이 화면이 얹는 조건은 「거른 것」으로 세지 않는다 — 그것까지 세면\n 아무도 없는 도메인이 「거른 결과가 없다」로 보여 첫 안내가 안 나온다. */\n this.filtered = filters.length > 0\n\n try {\n const response = (\n await client.query({\n query: gql`\n query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {\n responses: users(filters: $filters, pagination: $pagination, sortings: $sortings) {\n items {\n id\n username\n name\n email\n userType\n status\n lockedAt\n owner\n roles {\n id\n name\n }\n updatedAt\n }\n total\n }\n }\n `,\n variables: {\n filters: [\n ...filters,\n {\n name: 'userType',\n operator: 'notin',\n value: ['application', 'appliance']\n }\n ],\n pagination: { page, limit },\n sortings: sortings.length ? sortings : [{ name: 'name' }]\n }\n })\n ).data.responses\n\n this.total = response.total\n this.fetchState = 'loaded'\n\n return {\n records: response.items,\n total: response.total\n }\n } catch (e) {\n /* 못 읽은 것을 「없다」로 그리지 않는다. */\n this.fetchState = 'failed'\n throw e\n }\n }\n\n /**\n * 고른 사람의 역할을 고치는 자리를 연다.\n *\n * `domain-management` 가 격자 행에서 `ox-attribute-view` 를 여는 것과 같은 모양이다 —\n * `handlers.click` 에서 `openPopup` 에 template 을 넘긴다.\n *\n * `escapable` 은 넘기지 않는다. 이 앱의 기본값이 `false` 이고, 그것이 곧 **ESC 에 먼저\n * 묻는다**는 뜻이다 — 편집면에서 원하는 거동이 기본값이다.\n */\n private onSelectUser(record: any) {\n const popup = openPopup(\n html`\n <user-detail\n .user=${record}\n @close=${() => popup.close()}\n @user-updated=${() => {\n popup.close()\n /* 고친 사람만 다시 읽을 길이 없다 — 격자가 가진 것은 쪽 단위 새로고침뿐이다. */\n this.grist.fetch()\n }}\n ></user-detail>\n `,\n {\n backdrop: true,\n size: 'large',\n title: record?.name || record?.username || record?.email\n }\n )\n }\n\n /** 목록의 역할 편집과 같은 모양으로 연다 — `openPopup` 에 template 을 넘긴다. */\n private onInvite() {\n const popup = openPopup(\n html`\n <user-invite\n @close=${() => popup.close()}\n @user-invited=${() => {\n popup.close()\n this.grist.fetch()\n }}\n ></user-invite>\n `,\n {\n backdrop: true,\n size: 'large',\n title: i18next.t('label.add user')\n }\n )\n }\n}\n\n/** 소유자는 이름 옆에 표시한다. 서버가 사람마다 `owner` 를 채워 준다(공동 소유자도 함께). */\nfunction nameWithOwnerMark(value: string, _column: any, record: any) {\n return record?.owner\n ? html`<md-icon style=\"--md-icon-size: 16px; vertical-align: -3px;\">supervisor_account</md-icon> ${value}`\n : html`${value}`\n}\n\n/**\n * 잠긴 계정이 **언제 풀리는지**까지 말한다.\n *\n * 판정은 서버(`account-lock-rule.ts`)가 하고 여기서는 남은 시간만 보여 준다 —\n * 화면이 판정을 다시 하면 두 곳이 다른 답을 낸다. `lockedAt` 이 비어 있으면 자동 해제가\n * 없다는 뜻이므로 남은 시간을 말하지 않는다.\n */\nfunction statusRenderer(durationMinutes: () => number) {\n return (value: string, _column: any, record: any) => {\n if (value === 'locked') {\n return html`<span class=\"badge\" attention\n >${i18next.t('label.locked-user')}${lockLeft(record, durationMinutes())}</span\n >`\n }\n\n if (value === 'inactive') {\n return html`<span class=\"badge\" quiet>${i18next.t('label.inactive-user')}</span>`\n }\n\n /* 활성은 기본값이다 — 색을 쓰지 않는다. 색은 「무엇이 나를 기다리나」에만 쓴다. */\n return html`<span class=\"badge\">${i18next.t('label.activated-user', { defaultValue: value })}</span>`\n }\n}\n\n/** 잠금이 언제 풀리는지. 판정은 서버가 하고 여기서는 남은 시간만 말한다. */\nfunction lockLeft(record: any, minutes: number) {\n const lockedAt = record?.lockedAt ? new Date(record.lockedAt).getTime() : NaN\n\n if (Number.isNaN(lockedAt) || !(minutes > 0)) {\n /* `lockedAt` 이 없으면 자동 해제가 없다는 뜻이다 — 남은 시간을 지어내지 않는다. */\n return nothing\n }\n\n const left = Math.ceil((lockedAt + minutes * 60 * 1000 - Date.now()) / 60000)\n\n return left > 0 ? html` · ${i18next.t('text.unlocks in x minutes', { x: left })}` : nothing\n}\n\n/** 역할이 없는 것은 빈 칸이 아니라 **문장**으로 말한다. */\nfunction rolesRenderer(_value: any, _column: any, record: any) {\n const roles = record?.roles || []\n\n return roles.length\n ? html`${roles.map((role: any) => role.name).join(' · ')}`\n : html`<span class=\"badge\" attention>${i18next.t('text.no role assigned')}</span>`\n}\n"]}
|
|
1
|
+
{"version":3,"file":"user-list-page.js","sourceRoot":"","sources":["../../../client/pages/user/user-list-page.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@operato/data-grist'\nimport '@operato/oops/ox-oops-note.js'\nimport '../../components/user-detail.js'\nimport '../../components/user-invite.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, nothing } from 'lit'\nimport type { CSSResultGroup } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\n\nimport { client } from '@operato/graphql'\nimport { openPopup } from '@operato/layout'\nimport { i18next, localize } from '@operato/i18n'\nimport { p13n } from '@operato/p13n'\nimport { PageView } from '@operato/shell'\nimport { CommonGristStyles, CommonHeaderStyles, ScrollbarStyles } from '@operato/styles'\nimport { isMobileDevice } from '@operato/utils'\n\nimport type { DataGrist, FetchOption } from '@operato/data-grist'\n\n/**\n * 사용자 목록 — **서버가 거르고, 서버가 쪽을 나눈다.**\n *\n * ── 옛 화면이 하던 것 (`pages/user/user-management.ts`) ──────────────────────\n * `query { users { items { … } } }` 로 **도메인의 모든 사용자**를 한 번에 가져왔다. 한도도\n * 서버 검색도 없었고, 프레임워크가 이미 그것을 경고하고 있었다\n * (`unbounded list query on \"User\"`). 사용자는 권한(84행)과 달리 실제로 늘어난다.\n *\n * 그런데 **서버는 처음부터 받을 준비가 되어 있었다** — `users(filters:, pagination:, sortings:)`\n * 가 `ListParam` 을 받고 `name`·`email`·`description` 을 검색어로 본다\n * (`user-query.ts`). 화면이 안 쓰고 있었을 뿐이다.\n *\n * ── 왜 `ox-grist` 인가 ──────────────────────────────────────────────────────\n * 손으로 만든 목록이 잃는 것을 그것이 이미 갖고 있다 — 쪽 나누기, 정렬, 컬럼 걸러 보기,\n * 사람마다 기억하는 개인 설정, 카드↔표 모드, 내보내기. `figure-list-page` 가 같은 이유로\n * 옮겼고 그 판단을 그대로 따른다.\n *\n * ── 옛 화면의 탭을 없앤 이유 ────────────────────────────────────────────────\n * 탭이 `userType` 값으로 갈렸다. 그런데 그 칸은 `varchar` 이고 코드가 예상하는 넷\n * (`admin`·`user`·`application`·`appliance`) 밖의 값이거나 비어 있으면 **그 사람이 어느 탭에도\n * 안 나온다 — 화면에서 조용히 사라진다.**\n *\n * 여기서는 사람만 싣는다. 애플리케이션과 어플라이언스는 자기 화면(`applications`·`appliance`)에\n * 있고, 거기서 자기 자격증명까지 함께 다루는 것이 맞다. 다만 **거르는 것은 서버가 해야 한다** —\n * 지금은 `users` 가 종류를 안 가리므로 이 화면이 filter 로 요구한다.\n */\n/*\n * `as typeof PageView` 로 조이지 않는다 — 그렇게 하면 p13n 이 얹어 주는\n * `getPagePreferenceProvider` 가 타입에서 사라져 개인 설정(모드·정렬·쪽 크기를 사람마다\n * 기억하는 것)을 붙일 수 없다. `figure-list-page` 가 같은 주석을 달고 있다.\n */\nconst UserListPageBase = p13n(localize(i18next)(PageView))\n\n@customElement('user-list-page')\nexport class UserListPage extends UserListPageBase {\n static styles: CSSResultGroup = [\n ScrollbarStyles,\n CommonGristStyles,\n CommonHeaderStyles,\n css`\n :host {\n display: flex;\n flex-direction: column;\n width: 100%;\n background-color: var(--md-sys-color-surface);\n color: var(--md-sys-color-on-surface);\n /* 떠 있는 단추의 기준 상자 */\n position: relative;\n }\n\n ox-grist {\n flex: 1;\n min-height: 0;\n }\n\n /*\n ── 상태 배지 ──────────────────────────────────────────────────────────\n 색은 **짝으로** 쓴다(container / on-container). 한쪽만 쓰면 야간에 대비가 깨진다.\n 그리고 색은 「무엇이 나를 기다리나」에만 쓴다 — 활성은 기본값이라 색이 없다.\n 담는 면이 행이므로 모서리는 작게 잡는다.\n */\n .badge {\n display: inline-block;\n padding: 2px 8px;\n border-radius: var(--md-sys-shape-corner-small, 6px);\n font-size: 0.78rem;\n white-space: nowrap;\n background-color: var(--md-sys-color-surface-variant);\n color: var(--md-sys-color-on-surface-variant);\n }\n .badge[attention] {\n background-color: var(--md-sys-color-error-container);\n color: var(--md-sys-color-on-error-container);\n }\n .badge[quiet] {\n color: var(--md-sys-color-outline);\n }\n\n /* 격자를 떼지 않고 위에 겹친다 — 격자가 살아 있어야 다시 읽을 수 있다. */\n ox-oops-note[note] {\n position: absolute;\n inset: 0;\n z-index: 5;\n background-color: var(--md-sys-color-surface);\n }\n\n /*\n ── 사람 부르기 ────────────────────────────────────────────────────────\n 이 화면의 주된 동작 하나. 글자를 함께 둔다 — 더하기 표만으로는 무엇을 부르는지 모른다.\n 떠 있는 것의 모서리는 알약이다(담는 면을 따르지 않는다).\n */\n button[fab] {\n position: absolute;\n right: var(--spacing-large, 12px);\n bottom: var(--spacing-large, 12px);\n z-index: 10;\n\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 10px 18px 10px 14px;\n font: var(--label-font, inherit);\n font-size: 0.82rem;\n font-weight: 600;\n color: var(--md-sys-color-on-primary-container);\n background-color: var(--md-sys-color-primary-container);\n border: none;\n border-radius: var(--md-sys-shape-corner-full, 999px);\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.18);\n cursor: pointer;\n }\n button[fab]:hover {\n color: var(--md-sys-color-on-primary, #fff);\n background-color: var(--md-sys-color-primary);\n }\n button[fab] md-icon {\n --md-icon-size: 20px;\n }\n `\n ]\n\n @property({ type: Object }) config: any\n /*\n * 카드가 아니라 **행**이다. 카드는 그림으로 알아보는 물건(형상 카탈로그)의 모양이고,\n * 사람은 이름으로 알아본다. 좁은 화면에서는 `LIST` 가 같은 행을 접어 그린다.\n */\n @property({ type: String }) mode: 'CARD' | 'GRID' | 'LIST' = isMobileDevice() ? 'LIST' : 'GRID'\n\n @state() private lockDurationMinutes: number = 10\n\n /*\n * **읽는 중 · 못 읽었다 · 읽었는데 없다 를 가른다.** 셋을 한 문장으로 그리면 못 읽은 것을\n * 「없다」로 읽고, 사용자가 이미 있는 것을 만들러 간다.\n */\n @state() private fetchState: 'loading' | 'failed' | 'loaded' = 'loading'\n @state() private total: number = 0\n @state() private filtered: boolean = false\n\n @query('ox-grist') private grist!: DataGrist\n\n get context() {\n return {\n title: i18next.t('text.user management'),\n help: 'auth/users'\n }\n }\n\n render() {\n return html`\n <ox-grist\n .mode=${this.mode}\n .config=${this.config}\n .fetchHandler=${this.fetchHandler.bind(this)}\n .personalConfigProvider=${this.getPagePreferenceProvider('ox-grist')!}\n >\n <div slot=\"headroom\" class=\"header\">\n <div class=\"filters\">\n <ox-filters-form autofocus></ox-filters-form>\n </div>\n </div>\n\n <ox-grist-personalizer slot=\"setting\"></ox-grist-personalizer>\n </ox-grist>\n\n ${this.renderNote()}\n\n <button fab @click=${() => this.onInvite()}>\n <md-icon>person_add</md-icon>${i18next.t('label.add user')}\n </button>\n `\n }\n\n /**\n * 목록 위에 겹쳐 그린다 — 격자를 떼면 다시 읽을 길이 없어진다.\n *\n * 거른 결과가 비어 있는 것은 **가르칠 자리가 아니다.** 그때는 격자가 스스로 「없음」을\n * 말하게 두고, 여기서는 아무것도 그리지 않는다.\n */\n private renderNote() {\n if (this.fetchState === 'loading') {\n return nothing\n }\n\n if (this.fetchState === 'failed') {\n return html`<ox-oops-note\n note\n icon=\"error_outline\"\n title=${i18next.t('text.failed to fetch')}\n description=${i18next.t('text.please try again')}\n ></ox-oops-note>`\n }\n\n if (this.total === 0 && !this.filtered) {\n /* 새 도메인의 첫 화면. 소유자는 역할 없이도 모두 열리므로 그 사실부터 말한다. */\n return html`<ox-oops-note\n note\n icon=\"group_add\"\n title=${i18next.t('text.no other user yet')}\n description=${i18next.t('text.owner opens everything without a role')}\n ></ox-oops-note>`\n }\n\n return nothing\n }\n\n async pageInitialized() {\n this.config = {\n /*\n * 카드에 실을 것. 표 모드도 같은 컬럼 설정을 쓰므로 **두 모드가 다른 말을 하지 않는다.**\n */\n list: {\n fields: ['name', 'email'],\n details: ['status', 'roles']\n },\n columns: [\n { type: 'gutter', gutterName: 'sequence', fixed: true },\n {\n type: 'string',\n name: 'name',\n header: i18next.t('field.name'),\n record: { align: 'left', renderer: nameWithOwnerMark },\n filter: 'search',\n sortable: true,\n width: 160\n },\n {\n type: 'string',\n name: 'email',\n header: i18next.t('field.email'),\n record: { align: 'left' },\n filter: 'search',\n sortable: true,\n width: 220\n },\n {\n /*\n * 상태 하나로 두 가지를 말한다 — 지금 상태와, 잠긴 경우 **언제 풀리는지**.\n * `lockedAt` 과 설정된 잠금 시간으로 계산한다. 옛 화면은 「잠김」까지만 말했고,\n * 그래서 관리자가 기다리면 되는지 풀어 줘야 하는지 알 수 없었다.\n */\n type: 'string',\n name: 'status',\n header: i18next.t('field.status'),\n record: { align: 'center', renderer: statusRenderer(() => this.lockDurationMinutes) },\n filter: {\n options: ['', 'activated', 'locked', 'inactive'],\n multiple: false,\n operator: 'eq'\n },\n sortable: true,\n width: 140\n },\n {\n /*\n * 역할이 비어 있는 것을 **눈에 띄게** 그린다. 역할 없는 활성 계정은 로그인은 되는데\n * 화면이 하나도 없다 — 옛 화면에서는 그것이 빈 칸이라 아무도 알아채지 못했다.\n */\n type: 'string',\n name: 'roles',\n header: i18next.t('field.roles'),\n record: { align: 'left', renderer: rolesRenderer },\n width: 260\n },\n {\n type: 'datetime',\n name: 'updatedAt',\n header: i18next.t('field.updated_at'),\n record: { align: 'center' },\n sortable: true,\n width: 160\n }\n ],\n rows: {\n selectable: { multiple: false },\n handlers: {\n click: (_columns: any, _data: any, _column: any, record: any) => this.onSelectUser(record)\n }\n },\n sorters: [{ name: 'name' }]\n }\n }\n\n async pageUpdated(changes: any, lifecycle: any) {\n if (this.active) {\n await this.updateComplete\n this.grist.fetch()\n }\n }\n\n /**\n * 사람만 가져온다. **거르는 것은 서버가 한다** — 옛 화면은 전부 받아 클라이언트에서 나눴다.\n *\n * `userType` 은 `varchar` 라 값이 넷 밖으로 나갈 수 있다. 그래서 「기계가 아닌 것」을\n * `not-in` 으로 요구한다 — 「사람인 것」을 `in` 으로 요구하면 값이 비어 있는 사람이 사라진다.\n * (`userType` 을 enum 으로 못 박는 것은 §3 의 일이다.)\n */\n async fetchHandler({ filters = [], page, limit, sortings = [] }: FetchOption) {\n this.fetchState = 'loading'\n /* 사람만 보려고 이 화면이 얹는 조건은 「거른 것」으로 세지 않는다 — 그것까지 세면\n 아무도 없는 도메인이 「거른 결과가 없다」로 보여 첫 안내가 안 나온다. */\n this.filtered = filters.length > 0\n\n try {\n const response = (\n await client.query({\n query: gql`\n query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {\n responses: users(filters: $filters, pagination: $pagination, sortings: $sortings) {\n items {\n id\n username\n name\n email\n userType\n status\n lockedAt\n owner\n roles {\n id\n name\n }\n updatedAt\n }\n total\n }\n }\n `,\n variables: {\n filters: [\n ...filters,\n {\n name: 'userType',\n operator: 'notin',\n value: ['application', 'appliance']\n }\n ],\n pagination: { page, limit },\n sortings: sortings.length ? sortings : [{ name: 'name' }]\n }\n })\n ).data.responses\n\n this.total = response.total\n this.fetchState = 'loaded'\n\n return {\n records: response.items,\n total: response.total\n }\n } catch (e) {\n /* 못 읽은 것을 「없다」로 그리지 않는다. */\n this.fetchState = 'failed'\n throw e\n }\n }\n\n /**\n * 고른 사람의 역할을 고치는 자리를 연다.\n *\n * `domain-management` 가 격자 행에서 `ox-attribute-view` 를 여는 것과 같은 모양이다 —\n * `handlers.click` 에서 `openPopup` 에 template 을 넘긴다.\n *\n * `escapable` 은 넘기지 않는다. 이 앱의 기본값이 `false` 이고, 그것이 곧 **ESC 에 먼저\n * 묻는다**는 뜻이다 — 편집면에서 원하는 거동이 기본값이다.\n */\n private onSelectUser(record: any) {\n const popup = openPopup(\n html`\n <user-detail\n .user=${record}\n @close=${() => popup.close()}\n @user-updated=${() => {\n popup.close()\n /* 고친 사람만 다시 읽을 길이 없다 — 격자가 가진 것은 쪽 단위 새로고침뿐이다. */\n this.grist.fetch()\n }}\n ></user-detail>\n `,\n {\n backdrop: true,\n size: 'large',\n title: record?.name || record?.username || record?.email\n }\n )\n }\n\n /** 목록의 역할 편집과 같은 모양으로 연다 — `openPopup` 에 template 을 넘긴다. */\n private onInvite() {\n const popup = openPopup(\n html`\n <user-invite\n @close=${() => popup.close()}\n @user-invited=${() => {\n popup.close()\n this.grist.fetch()\n }}\n ></user-invite>\n `,\n {\n backdrop: true,\n size: 'large',\n title: i18next.t('label.add user')\n }\n )\n }\n}\n\n/** 소유자는 이름 옆에 표시한다. 서버가 사람마다 `owner` 를 채워 준다(공동 소유자도 함께). */\nfunction nameWithOwnerMark(value: string, _column: any, record: any) {\n return record?.owner\n ? html`<md-icon style=\"--md-icon-size: 16px; vertical-align: -3px;\">supervisor_account</md-icon> ${value}`\n : html`${value}`\n}\n\n/**\n * 잠긴 계정이 **언제 풀리는지**까지 말한다.\n *\n * 판정은 서버(`account-lock-rule.ts`)가 하고 여기서는 남은 시간만 보여 준다 —\n * 화면이 판정을 다시 하면 두 곳이 다른 답을 낸다. `lockedAt` 이 비어 있으면 자동 해제가\n * 없다는 뜻이므로 남은 시간을 말하지 않는다.\n */\nfunction statusRenderer(durationMinutes: () => number) {\n return (value: string, _column: any, record: any) => {\n if (value === 'locked') {\n return html`<span class=\"badge\" attention\n >${i18next.t('label.locked-user')}${lockLeft(record, durationMinutes())}</span\n >`\n }\n\n if (value === 'inactive') {\n return html`<span class=\"badge\" quiet>${i18next.t('label.inactive-user')}</span>`\n }\n\n /* 활성은 기본값이다 — 색을 쓰지 않는다. 색은 「무엇이 나를 기다리나」에만 쓴다. */\n return html`<span class=\"badge\">${i18next.t('label.activated-user', { defaultValue: value })}</span>`\n }\n}\n\n/** 잠금이 언제 풀리는지. 판정은 서버가 하고 여기서는 남은 시간만 말한다. */\nfunction lockLeft(record: any, minutes: number) {\n const lockedAt = record?.lockedAt ? new Date(record.lockedAt).getTime() : NaN\n\n if (Number.isNaN(lockedAt) || !(minutes > 0)) {\n /* `lockedAt` 이 없으면 자동 해제가 없다는 뜻이다 — 남은 시간을 지어내지 않는다. */\n return nothing\n }\n\n const left = Math.ceil((lockedAt + minutes * 60 * 1000 - Date.now()) / 60000)\n\n return left > 0 ? html` · ${i18next.t('text.unlocks in x minutes', { x: left })}` : nothing\n}\n\n/** 역할이 없는 것은 빈 칸이 아니라 **문장**으로 말한다. */\nfunction rolesRenderer(_value: any, _column: any, record: any) {\n const roles = record?.roles || []\n\n return roles.length\n ? html`${roles.map((role: any) => role.name).join(' · ')}`\n : html`<span class=\"badge\" attention>${i18next.t('text.no role assigned')}</span>`\n}\n"]}
|