@things-factory/organization 10.1.256 → 10.1.258

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/dist-client/component/approval-line-items-editor.d.ts +2 -2
  2. package/dist-client/component/approval-line-items-editor.js +16 -24
  3. package/dist-client/component/approval-line-items-editor.js.map +1 -1
  4. package/dist-client/component/department-selector.d.ts +2 -2
  5. package/dist-client/component/department-selector.js +48 -46
  6. package/dist-client/component/department-selector.js.map +1 -1
  7. package/dist-client/entity360/department-360.d.ts +3 -4
  8. package/dist-client/entity360/department-360.js +28 -41
  9. package/dist-client/entity360/department-360.js.map +1 -1
  10. package/dist-client/entity360/person-membership-section.d.ts +3 -4
  11. package/dist-client/entity360/person-membership-section.js +28 -42
  12. package/dist-client/entity360/person-membership-section.js.map +1 -1
  13. package/dist-client/pages/employee/employee-list-page.d.ts +7 -9
  14. package/dist-client/pages/employee/employee-list-page.js +42 -69
  15. package/dist-client/pages/employee/employee-list-page.js.map +1 -1
  16. package/dist-client/pages/employee-profile/my-employee-profile-page.d.ts +11 -12
  17. package/dist-client/pages/employee-profile/my-employee-profile-page.js +53 -108
  18. package/dist-client/pages/employee-profile/my-employee-profile-page.js.map +1 -1
  19. package/dist-client/tsconfig.tsbuildinfo +1 -1
  20. package/package.json +10 -10
  21. package/tests/department-query-shape.test.ts +2 -2
  22. package/translations/en.json +0 -2
  23. package/translations/es.json +0 -2
  24. package/translations/ja.json +0 -2
  25. package/translations/ko.json +0 -2
  26. package/translations/ms.json +0 -2
  27. package/translations/zh.json +0 -2
@@ -1,14 +1,14 @@
1
- import { __decorate, __metadata } from "tslib";
1
+ import { __decorate } from "tslib";
2
2
  import '@material/web/icon/icon.js';
3
3
  import '@operato/list-state/ox-empty-note.js';
4
4
  import '@operato/list-state/ox-page-state.js';
5
5
  import gql from 'graphql-tag';
6
6
  import { css, html, nothing } from 'lit';
7
- import { customElement, state } from 'lit/decorators.js';
8
- import { client } from '@operato/graphql';
7
+ import { customElement } from 'lit/decorators.js';
8
+ import { MutationController, QueryController } from '@operato/graphql';
9
9
  import { i18next, localize } from '@operato/i18n';
10
10
  import { notify } from '@operato/layout';
11
- import { PageView } from '@operato/shell';
11
+ import { PageView, DraftController } from '@operato/shell';
12
12
  import { ButtonStyles, DocumentPageStyles, FormFieldStyles } from '@operato/styles';
13
13
  import { getLanguages } from '@things-factory/auth-base/dist-client';
14
14
  import { addSkills, blankRow, completenessOf, headlineOf, periodLabelOf, PROFILE_SECTIONS, profileFormOf, profileInputOf } from './employee-profile-form';
@@ -37,17 +37,21 @@ const SUMMARY_LIMIT = 500;
37
37
  let MyEmployeeProfilePage = class MyEmployeeProfilePage extends localize(i18next)(PageView) {
38
38
  constructor() {
39
39
  super(...arguments);
40
- this.form = profileFormOf(null);
41
- /** Who the profile is about - the employee record's basics. */
42
- this.me = null;
43
- /** Whether the account is linked to an employee record - unknown until read. */
44
- this.linked = null;
45
- this.loading = false;
46
- this.error = null;
47
- this.busy = false;
48
- this.updatedAt = null;
49
- /** The form as last read or saved - what is written differs from it until saved. */
50
- this.saved = JSON.stringify(profileInputOf(profileFormOf(null)));
40
+ /* Who the account is here and its profile (ADR-0190). */
41
+ this.profile = new QueryController(this, gql `
42
+ query {
43
+ myEmployees { id name jobPosition jobResponsibility hiredOn department { name } }
44
+ myEmployeeProfile { ${PROFILE_FIELDS} }
45
+ }
46
+ `);
47
+ /* Saving reads the profile again: the form is then the profile as saved. */
48
+ this.saving = new MutationController(this, gql `
49
+ mutation ($profile: EmployeeProfileInput!) {
50
+ saveMyEmployeeProfile(profile: $profile) { ${PROFILE_FIELDS} }
51
+ }
52
+ `, { refetch: () => [this.profile] });
53
+ /** What is written over the profile read; a save clears what it sent. */
54
+ this.typed = new DraftController(this, { read: () => this.held });
51
55
  }
52
56
  static { this.styles = [
53
57
  FormFieldStyles,
@@ -350,8 +354,29 @@ let MyEmployeeProfilePage = class MyEmployeeProfilePage extends localize(i18next
350
354
  }
351
355
  `
352
356
  ]; }
357
+ /** The profile as last read or saved. */
358
+ get held() {
359
+ return profileFormOf(this.profile.data?.myEmployeeProfile);
360
+ }
361
+ get form() {
362
+ return { ...this.held, ...this.typed.edits };
363
+ }
364
+ /** Who the profile is about - the employee record's basics. */
365
+ get me() {
366
+ return this.profile.data?.myEmployees?.[0] ?? null;
367
+ }
368
+ /** Whether the account is linked to an employee record - unknown until read. */
369
+ get linked() {
370
+ return this.profile.data ? !!this.me : null;
371
+ }
372
+ get updatedAt() {
373
+ return this.profile.data?.myEmployeeProfile?.updatedAt ?? null;
374
+ }
375
+ get busy() {
376
+ return this.saving.pending;
377
+ }
353
378
  get dirty() {
354
- return JSON.stringify(profileInputOf(this.form)) !== this.saved;
379
+ return JSON.stringify(profileInputOf(this.form)) !== JSON.stringify(profileInputOf(this.held));
355
380
  }
356
381
  get context() {
357
382
  return {
@@ -360,68 +385,14 @@ let MyEmployeeProfilePage = class MyEmployeeProfilePage extends localize(i18next
360
385
  actions: this.linked ? [{ title: i18next.t('button.save'), icon: 'save', emphasis: { raised: true }, action: () => this.save() }] : []
361
386
  };
362
387
  }
363
- async pageUpdated(changes) {
364
- if (this.active && changes.active)
365
- this.load();
366
- }
367
- async load() {
368
- this.loading = true;
369
- this.error = null;
370
- try {
371
- const response = await client.query({
372
- query: gql `
373
- query {
374
- myEmployees { id name jobPosition jobResponsibility hiredOn department { name } }
375
- myEmployeeProfile { ${PROFILE_FIELDS} }
376
- }
377
- `,
378
- fetchPolicy: 'no-cache'
379
- });
380
- if (response.errors?.length)
381
- throw response.errors[0];
382
- this.me = response.data?.myEmployees?.[0] ?? null;
383
- this.linked = !!this.me;
384
- this.hold(response.data?.myEmployeeProfile);
385
- }
386
- catch (error) {
387
- this.error = error;
388
- }
389
- finally {
390
- this.loading = false;
391
- this.updateContext();
392
- }
393
- }
394
- hold(profile) {
395
- this.form = profileFormOf(profile);
396
- this.saved = JSON.stringify(profileInputOf(this.form));
397
- this.updatedAt = profile?.updatedAt ?? null;
398
- }
399
388
  async save() {
400
389
  if (this.busy)
401
390
  return false;
402
- this.busy = true;
403
- try {
404
- const response = await client.mutate({
405
- mutation: gql `
406
- mutation ($profile: EmployeeProfileInput!) {
407
- saveMyEmployeeProfile(profile: $profile) { ${PROFILE_FIELDS} }
408
- }
409
- `,
410
- variables: { profile: profileInputOf(this.form) }
411
- });
412
- if (response.errors?.length)
413
- throw response.errors[0];
414
- this.hold(response.data.saveMyEmployeeProfile);
415
- notify({ message: i18next.t('text.saved') });
416
- return true;
417
- }
418
- catch (error) {
419
- notify({ level: 'error', message: error?.message ?? String(error) });
391
+ const profile = profileInputOf(this.form);
392
+ if (!(await this.typed.submit(() => this.saving.run({ profile }))))
420
393
  return false;
421
- }
422
- finally {
423
- this.busy = false;
424
- }
394
+ notify({ message: i18next.t('text.saved') });
395
+ return true;
425
396
  }
426
397
  /** The document as sales will hand it in - what is written is saved first, so the preview shows it. */
427
398
  async preview() {
@@ -436,7 +407,7 @@ let MyEmployeeProfilePage = class MyEmployeeProfilePage extends localize(i18next
436
407
  section?.querySelector('textarea, input:not([type="checkbox"])')?.focus({ preventScroll: true });
437
408
  }
438
409
  render() {
439
- return html `<ox-page-state .loading=${this.loading && this.linked === null} .error=${this.linked === null ? this.error : null} @retry=${() => this.load()}>
410
+ return html `<ox-page-state .loading=${this.profile.loading && this.linked === null} .error=${this.linked === null ? (this.profile.error ?? null) : null} @retry=${() => this.profile.refetch()}>
440
411
  ${this.linked === false
441
412
  ? html `<ox-empty-note
442
413
  icon="badge"
@@ -503,14 +474,16 @@ let MyEmployeeProfilePage = class MyEmployeeProfilePage extends localize(i18next
503
474
  maxlength=${SUMMARY_LIMIT}
504
475
  placeholder=${i18next.t('text.employee-profile-summary-example')}
505
476
  .value=${summary}
506
- @input=${(e) => (this.form = { ...this.form, summary: e.target.value })}
477
+ @input=${(e) => this.typed.set('summary', e.target.value)}
507
478
  ></textarea>
508
479
  </section>`;
509
480
  }
510
481
  renderSkills() {
511
482
  const skills = this.form.skills;
512
483
  const add = (input) => {
513
- this.form = { ...this.form, skills: addSkills(skills, input.value) };
484
+ const next = addSkills(skills, input.value);
485
+ if (next.length !== skills.length)
486
+ this.typed.set('skills', next);
514
487
  input.value = '';
515
488
  };
516
489
  return html `<section id="skills" class="page-section">
@@ -525,7 +498,7 @@ let MyEmployeeProfilePage = class MyEmployeeProfilePage extends localize(i18next
525
498
  unstyled
526
499
  title=${i18next.t('button.delete')}
527
500
  aria-label=${`${i18next.t('button.delete')} ${skill}`}
528
- @click=${() => (this.form = { ...this.form, skills: skills.filter(one => one !== skill) })}
501
+ @click=${() => this.typed.set('skills', skills.filter(one => one !== skill))}
529
502
  >
530
503
  <md-icon>close</md-icon>
531
504
  </button></span
@@ -540,7 +513,7 @@ let MyEmployeeProfilePage = class MyEmployeeProfilePage extends localize(i18next
540
513
  add(input);
541
514
  }
542
515
  else if (e.key === 'Backspace' && !input.value && skills.length) {
543
- this.form = { ...this.form, skills: skills.slice(0, -1) };
516
+ this.typed.set('skills', skills.slice(0, -1));
544
517
  }
545
518
  }}
546
519
  @blur=${(e) => add(e.target)}
@@ -552,7 +525,7 @@ let MyEmployeeProfilePage = class MyEmployeeProfilePage extends localize(i18next
552
525
  const rows = this.form[section.key];
553
526
  /* Each change reads the rows as they are now - two inputs before a render must not undo each other. */
554
527
  const current = () => this.form[section.key];
555
- const setRows = (next) => (this.form = { ...this.form, [section.key]: next });
528
+ const setRows = (next) => this.typed.set(section.key, next);
556
529
  const change = (index, patch) => setRows(current().map((row, at) => (at === index ? { ...row, ...patch } : row)));
557
530
  const remove = (index) => setRows(current().filter((_, at) => at !== index));
558
531
  /* The newest first, as the document is read; its first field takes the cursor. */
@@ -621,34 +594,6 @@ let MyEmployeeProfilePage = class MyEmployeeProfilePage extends localize(i18next
621
594
  </div>`;
622
595
  }
623
596
  };
624
- __decorate([
625
- state(),
626
- __metadata("design:type", Object)
627
- ], MyEmployeeProfilePage.prototype, "form", void 0);
628
- __decorate([
629
- state(),
630
- __metadata("design:type", Object)
631
- ], MyEmployeeProfilePage.prototype, "me", void 0);
632
- __decorate([
633
- state(),
634
- __metadata("design:type", Object)
635
- ], MyEmployeeProfilePage.prototype, "linked", void 0);
636
- __decorate([
637
- state(),
638
- __metadata("design:type", Object)
639
- ], MyEmployeeProfilePage.prototype, "loading", void 0);
640
- __decorate([
641
- state(),
642
- __metadata("design:type", Object)
643
- ], MyEmployeeProfilePage.prototype, "error", void 0);
644
- __decorate([
645
- state(),
646
- __metadata("design:type", Object)
647
- ], MyEmployeeProfilePage.prototype, "busy", void 0);
648
- __decorate([
649
- state(),
650
- __metadata("design:type", Object)
651
- ], MyEmployeeProfilePage.prototype, "updatedAt", void 0);
652
597
  MyEmployeeProfilePage = __decorate([
653
598
  customElement('my-employee-profile-page')
654
599
  ], MyEmployeeProfilePage);
@@ -1 +1 @@
1
- {"version":3,"file":"my-employee-profile-page.js","sourceRoot":"","sources":["../../../client/pages/employee-profile/my-employee-profile-page.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@operato/list-state/ox-empty-note.js'\nimport '@operato/list-state/ox-page-state.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, nothing } from 'lit'\nimport { customElement, state } from 'lit/decorators.js'\n\nimport { client } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { notify } from '@operato/layout'\nimport { PageView } from '@operato/shell'\nimport { ButtonStyles, DocumentPageStyles, FormFieldStyles } from '@operato/styles'\nimport { getLanguages } from '@things-factory/auth-base/dist-client'\n\nimport {\n addSkills,\n blankRow,\n completenessOf,\n headlineOf,\n periodLabelOf,\n PROFILE_SECTIONS,\n profileFormOf,\n profileInputOf,\n type ProfileField,\n type ProfileForm,\n type ProfileRow,\n type ProfileSection\n} from './employee-profile-form'\nimport { readerLanguageOf, type Language } from './employee-profile-files'\nimport { openProfilePreview } from './employee-profile-preview'\n\nconst PROFILE_FIELDS = `\n id\n summary\n skills\n educations { school major degree startedOn endedOn }\n certificates { name issuer acquiredOn }\n careers { company department position startedOn endedOn duties }\n projects { name client startedOn endedOn role duties technologies }\n updatedAt\n`\n\nconst SUMMARY_LIMIT = 500\n\n/**\n * 내 프로필 — the employee writes their own career profile: a summary, skills, and the rows of education,\n * certificates, past jobs and projects; sales attaches it to quotations (ADR-0169 decisions 2 · 5). Saved whole.\n *\n * The page is built to be filled in: who it is about and how complete it is stand at the top, each missing essential a\n * click away; each row is a card whose headline reads like the document's line; a section with nothing yet says what\n * it is for; and the document itself is one click away (PDF), as sales will hand it in. An account not linked to an\n * employee record has no profile to write, and is told so before anything is typed.\n */\n@customElement('my-employee-profile-page')\nexport class MyEmployeeProfilePage extends localize(i18next)(PageView) {\n static styles = [\n FormFieldStyles,\n ButtonStyles,\n DocumentPageStyles,\n css`\n label {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-small, 4px);\n min-width: 0;\n font-size: var(--ui-t-small, 11.5px);\n color: var(--ui-muted, var(--md-sys-color-on-surface-variant));\n }\n label > :is(input, textarea) {\n font-size: var(--ui-t-body, 13px);\n color: var(--ui-ink, var(--md-sys-color-on-surface));\n }\n input,\n textarea {\n box-sizing: border-box;\n width: 100%;\n font-family: inherit;\n }\n input:not([type='checkbox']) {\n height: var(--form-element-height-medium, 32px);\n }\n textarea {\n min-height: 72px;\n resize: vertical;\n line-height: 1.5;\n }\n .muted {\n color: var(--ui-muted, var(--md-sys-color-on-surface-variant));\n }\n\n /* ── Who it is about, and how far along ─────────────────────────────── */\n .hero {\n display: grid;\n grid-template-columns: minmax(0, 1fr) minmax(240px, 340px);\n gap: var(--spacing-huge, 24px);\n align-items: center;\n }\n .who {\n display: flex;\n align-items: center;\n gap: var(--spacing-large, 12px);\n min-width: 0;\n }\n .avatar {\n flex: none;\n display: grid;\n place-items: center;\n width: 56px;\n height: 56px;\n border-radius: 50%;\n background: var(--md-sys-color-primary-container);\n color: var(--md-sys-color-on-primary-container);\n font-size: 22px;\n font-weight: 700;\n }\n .who h2 {\n margin: 0;\n font-size: var(--ui-t-headline, 20px);\n font-weight: 700;\n color: var(--ui-ink, var(--md-sys-color-on-surface));\n }\n .who p {\n margin: 2px 0 0;\n font-size: var(--ui-t-body, 13px);\n }\n .progress {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-small, 4px);\n }\n .progress-head {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n font-size: var(--ui-t-small, 11.5px);\n }\n .progress-head strong {\n font-size: var(--ui-t-title, 15px);\n color: var(--ui-ink, var(--md-sys-color-on-surface));\n }\n .bar {\n height: 8px;\n border-radius: 4px;\n background: var(--md-sys-color-surface-container-high);\n overflow: hidden;\n }\n .bar > span {\n display: block;\n height: 100%;\n border-radius: 4px;\n background: var(--md-sys-color-primary);\n transition: width 0.3s ease;\n }\n .missing {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--spacing-small, 4px);\n margin-top: var(--spacing-small, 4px);\n font-size: var(--ui-t-small, 11.5px);\n }\n .hero-foot {\n grid-column: 1 / -1;\n display: flex;\n align-items: center;\n gap: var(--spacing-medium, 8px);\n padding-top: var(--spacing-large, 12px);\n border-top: 1px solid var(--ui-line, var(--md-sys-color-outline-variant));\n font-size: var(--ui-t-small, 11.5px);\n }\n .hero-foot .unsaved {\n color: var(--md-sys-color-tertiary, var(--md-sys-color-primary));\n font-weight: 600;\n }\n .hero-foot button {\n margin-left: auto;\n }\n\n /* ── A section ─────────────────────────────────────────────────────── */\n .page-section-header h3 {\n display: flex;\n align-items: center;\n gap: var(--spacing-medium, 8px);\n }\n .page-section-header h3 md-icon {\n --md-icon-size: 20px;\n color: var(--md-sys-color-primary);\n }\n .count {\n min-width: 20px;\n padding: 1px 7px;\n border-radius: 10px;\n background: var(--md-sys-color-surface-container-high);\n font-size: var(--ui-t-small, 11.5px);\n font-weight: 600;\n text-align: center;\n color: var(--ui-muted, var(--md-sys-color-on-surface-variant));\n }\n .counter {\n margin-left: auto;\n font-size: var(--ui-t-small, 11.5px);\n }\n .summary textarea {\n min-height: 132px;\n }\n\n /* Skills as chips, the field to add more inside the same box. */\n .chips {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--spacing-small, 4px);\n min-height: 88px;\n align-content: flex-start;\n box-sizing: border-box;\n padding: var(--spacing-medium, 8px);\n border: 1px solid var(--md-sys-color-outline);\n border-radius: var(--input-field-border-radius, var(--md-sys-shape-corner-small, 4px));\n background: var(--md-sys-color-surface-container-highest);\n }\n .chips:focus-within {\n border-color: var(--md-sys-color-primary);\n }\n .chip {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n height: 26px;\n padding: 0 4px 0 10px;\n border-radius: 13px;\n background: var(--md-sys-color-secondary-container);\n color: var(--md-sys-color-on-secondary-container);\n font-size: var(--ui-t-body, 13px);\n }\n .chip button {\n height: 20px;\n width: 20px;\n padding: 0;\n border: 0;\n background: transparent;\n }\n .chip button md-icon {\n --md-icon-size: 14px;\n color: inherit;\n }\n .chips input {\n flex: 1 1 140px;\n width: auto;\n height: 26px;\n border: 0;\n padding: 0 4px;\n background: transparent;\n outline: none;\n }\n\n /* A row of a section is a card: its line as the document reads it, then its fields. */\n .entries {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-medium, 8px);\n }\n .entry {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-large, 12px);\n }\n .entry-head {\n display: flex;\n align-items: center;\n gap: var(--spacing-medium, 8px);\n min-width: 0;\n }\n .entry-head strong {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: var(--ui-t-body, 13px);\n color: var(--ui-ink, var(--md-sys-color-on-surface));\n }\n .entry-head strong.untitled {\n font-weight: 400;\n color: var(--ui-muted, var(--md-sys-color-on-surface-variant));\n }\n .entry-head .period {\n flex: none;\n font-size: var(--ui-t-small, 11.5px);\n }\n .entry-head button {\n margin-left: auto;\n }\n .fields {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));\n gap: var(--spacing-medium, 8px) var(--spacing-large, 12px);\n }\n .fields .wide {\n grid-column: 1 / -1;\n }\n .fields .start {\n grid-column-start: 1;\n }\n .end {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n gap: var(--spacing-medium, 8px);\n align-items: end;\n min-width: 0;\n }\n .ongoing {\n flex-direction: row;\n align-items: center;\n gap: var(--spacing-small, 4px);\n height: var(--form-element-height-medium, 32px);\n font-size: var(--ui-t-body, 13px);\n white-space: nowrap;\n color: var(--ui-ink, var(--md-sys-color-on-surface));\n }\n .ongoing input {\n width: auto;\n }\n\n /* A section with nothing yet: what it is for, and the way to begin. */\n .empty-slot {\n display: flex;\n align-items: center;\n gap: var(--spacing-large, 12px);\n padding: var(--spacing-large, 12px) 16px;\n border: 1px dashed var(--md-sys-color-outline-variant);\n border-radius: var(--ui-r, 10px);\n font-size: var(--ui-t-body, 13px);\n }\n .empty-slot button {\n margin-left: auto;\n }\n\n @media (max-width: 720px) {\n .hero {\n grid-template-columns: 1fr;\n }\n .empty-slot {\n flex-direction: column;\n align-items: flex-start;\n }\n .empty-slot button {\n margin-left: 0;\n }\n }\n @media (pointer: coarse) {\n input,\n textarea {\n font-size: 16px;\n }\n }\n `\n ]\n\n @state() form: ProfileForm = profileFormOf(null)\n /** Who the profile is about - the employee record's basics. */\n @state() me: any = null\n /** Whether the account is linked to an employee record - unknown until read. */\n @state() linked: boolean | null = null\n @state() loading = false\n @state() error: unknown = null\n @state() busy = false\n @state() updatedAt: string | null = null\n /** The form as last read or saved - what is written differs from it until saved. */\n private saved = JSON.stringify(profileInputOf(profileFormOf(null)))\n\n private get dirty() {\n return JSON.stringify(profileInputOf(this.form)) !== this.saved\n }\n\n get context() {\n return {\n title: i18next.t('title.my-employee-profile'),\n frame: 'document',\n actions: this.linked ? [{ title: i18next.t('button.save'), icon: 'save', emphasis: { raised: true }, action: () => this.save() }] : []\n }\n }\n\n async pageUpdated(changes: any) {\n if ((this as any).active && changes.active) this.load()\n }\n\n private async load() {\n this.loading = true\n this.error = null\n try {\n const response = await client.query({\n query: gql`\n query {\n myEmployees { id name jobPosition jobResponsibility hiredOn department { name } }\n myEmployeeProfile { ${PROFILE_FIELDS} }\n }\n `,\n fetchPolicy: 'no-cache'\n })\n if (response.errors?.length) throw response.errors[0]\n this.me = response.data?.myEmployees?.[0] ?? null\n this.linked = !!this.me\n this.hold(response.data?.myEmployeeProfile)\n } catch (error) {\n this.error = error\n } finally {\n this.loading = false\n this.updateContext()\n }\n }\n\n private hold(profile: any) {\n this.form = profileFormOf(profile)\n this.saved = JSON.stringify(profileInputOf(this.form))\n this.updatedAt = profile?.updatedAt ?? null\n }\n\n private async save(): Promise<boolean> {\n if (this.busy) return false\n this.busy = true\n try {\n const response = await client.mutate({\n mutation: gql`\n mutation ($profile: EmployeeProfileInput!) {\n saveMyEmployeeProfile(profile: $profile) { ${PROFILE_FIELDS} }\n }\n `,\n variables: { profile: profileInputOf(this.form) }\n })\n if (response.errors?.length) throw response.errors[0]\n this.hold(response.data.saveMyEmployeeProfile)\n notify({ message: i18next.t('text.saved') })\n return true\n } catch (error: any) {\n notify({ level: 'error', message: error?.message ?? String(error) })\n return false\n } finally {\n this.busy = false\n }\n }\n\n /** The document as sales will hand it in - what is written is saved first, so the preview shows it. */\n private async preview() {\n if (this.dirty && !(await this.save())) return\n const languages = ((await getLanguages()) ?? []) as Language[]\n openProfilePreview({ own: true }, languages, readerLanguageOf(languages))\n }\n\n private goTo(key: string) {\n const section = this.renderRoot.querySelector<HTMLElement>(`#${key}`)\n section?.scrollIntoView({ behavior: 'smooth', block: 'start' })\n section?.querySelector<HTMLElement>('textarea, input:not([type=\"checkbox\"])')?.focus({ preventScroll: true })\n }\n\n render() {\n return html`<ox-page-state .loading=${this.loading && this.linked === null} .error=${this.linked === null ? this.error : null} @retry=${() => this.load()}>\n ${this.linked === false\n ? html`<ox-empty-note\n icon=\"badge\"\n title=${i18next.t('text.employee-profile-not-linked-title')}\n description=${i18next.t('text.employee-profile-not-linked-description')}\n ></ox-empty-note>`\n : this.linked\n ? html`<div class=\"page\">\n ${this.renderHero()}\n <div class=\"page-section-grid\">${this.renderSummary()} ${this.renderSkills()}</div>\n ${PROFILE_SECTIONS.map(section => this.renderSection(section))}\n </div>`\n : nothing}\n </ox-page-state>`\n }\n\n private renderHero() {\n const me = this.me ?? {}\n const { percent, missing } = completenessOf(this.form)\n const role = [me.jobPosition, me.jobResponsibility].filter(Boolean).join(' / ')\n const day = (value?: string | null) => (value ? String(value).slice(0, 10).replace(/-/g, '.') : '')\n\n return html`<section class=\"hero page-card\">\n <div class=\"who\">\n <div class=\"avatar\" aria-hidden=\"true\">${(me.name ?? '?').trim().slice(0, 1)}</div>\n <div>\n <h2>${me.name ?? ''}</h2>\n <p class=\"muted\">${[me.department?.name, role].filter(Boolean).join(' · ')}</p>\n ${me.hiredOn ? html`<p class=\"muted\">${i18next.t('label.hired-on')} ${day(me.hiredOn)}</p>` : nothing}\n </div>\n </div>\n\n <div class=\"progress\">\n <div class=\"progress-head\">\n <span class=\"muted\">${i18next.t('text.employee-profile-completeness')}</span>\n <strong>${percent}%</strong>\n </div>\n <div class=\"bar\" role=\"progressbar\" aria-valuemin=\"0\" aria-valuemax=\"100\" aria-valuenow=${percent}><span style=\"width:${percent}%\"></span></div>\n ${missing.length\n ? html`<div class=\"missing\">\n <span class=\"muted\">${i18next.t('text.employee-profile-to-fill')}</span>\n ${missing.map(key => html`<button small quiet @click=${() => this.goTo(key)}>${i18next.t(`label.employee-profile-${key}`)}</button>`)}\n </div>`\n : html`<div class=\"missing muted\">${i18next.t('text.employee-profile-ready')}</div>`}\n </div>\n\n <div class=\"hero-foot\">\n ${this.dirty\n ? html`<span class=\"unsaved\">${i18next.t('text.employee-profile-unsaved')}</span>`\n : this.updatedAt\n ? html`<span class=\"muted\">${i18next.t('label.employee-profile-updated-at')} ${day(this.updatedAt)}</span>`\n : nothing}\n <button ?disabled=${this.busy} @click=${() => this.preview()}><md-icon>preview</md-icon>${i18next.t('button.employee-profile-preview')}</button>\n </div>\n </section>`\n }\n\n private renderSummary() {\n const summary = this.form.summary\n return html`<section id=\"summary\" class=\"page-section summary\">\n <header class=\"page-section-header\">\n <h3><md-icon>person</md-icon>${i18next.t('label.employee-profile-summary')}</h3>\n <span class=\"counter muted\">${summary.length} / ${SUMMARY_LIMIT}</span>\n </header>\n <textarea\n aria-label=${i18next.t('label.employee-profile-summary')}\n maxlength=${SUMMARY_LIMIT}\n placeholder=${i18next.t('text.employee-profile-summary-example')}\n .value=${summary}\n @input=${(e: any) => (this.form = { ...this.form, summary: e.target.value })}\n ></textarea>\n </section>`\n }\n\n private renderSkills() {\n const skills = this.form.skills\n const add = (input: HTMLInputElement) => {\n this.form = { ...this.form, skills: addSkills(skills, input.value) }\n input.value = ''\n }\n\n return html`<section id=\"skills\" class=\"page-section\">\n <header class=\"page-section-header\">\n <h3><md-icon>construction</md-icon>${i18next.t('label.employee-profile-skills')}</h3>\n ${skills.length ? html`<span class=\"count\">${skills.length}</span>` : nothing}\n </header>\n <div class=\"chips\" @click=${(e: Event) => (e.currentTarget as HTMLElement).querySelector('input')?.focus()}>\n ${skills.map(\n skill => html`<span class=\"chip\"\n >${skill}\n <button\n unstyled\n title=${i18next.t('button.delete')}\n aria-label=${`${i18next.t('button.delete')} ${skill}`}\n @click=${() => (this.form = { ...this.form, skills: skills.filter(one => one !== skill) })}\n >\n <md-icon>close</md-icon>\n </button></span\n >`\n )}\n <input\n aria-label=${i18next.t('label.employee-profile-skills')}\n placeholder=${i18next.t('text.employee-profile-skill-placeholder')}\n @keydown=${(e: KeyboardEvent) => {\n const input = e.target as HTMLInputElement\n if ((e.key === 'Enter' || e.key === ',') && !e.isComposing) {\n e.preventDefault()\n add(input)\n } else if (e.key === 'Backspace' && !input.value && skills.length) {\n this.form = { ...this.form, skills: skills.slice(0, -1) }\n }\n }}\n @blur=${(e: Event) => add(e.target as HTMLInputElement)}\n />\n </div>\n </section>`\n }\n\n private renderSection(section: ProfileSection) {\n const rows = this.form[section.key]\n /* Each change reads the rows as they are now - two inputs before a render must not undo each other. */\n const current = () => this.form[section.key]\n const setRows = (next: ProfileRow[]) => (this.form = { ...this.form, [section.key]: next })\n const change = (index: number, patch: ProfileRow) => setRows(current().map((row, at) => (at === index ? { ...row, ...patch } : row)))\n const remove = (index: number) => setRows(current().filter((_, at) => at !== index))\n /* The newest first, as the document is read; its first field takes the cursor. */\n const add = async () => {\n setRows([blankRow(section), ...current()])\n await this.updateComplete\n this.renderRoot.querySelector<HTMLInputElement>(`#${section.key} .entry input`)?.focus()\n }\n\n return html`<section id=${section.key} class=\"page-section\">\n <header class=\"page-section-header\">\n <h3><md-icon>${section.icon}</md-icon>${i18next.t(section.title)}</h3>\n ${rows.length ? html`<span class=\"count\">${rows.length}</span>` : nothing}\n ${rows.length\n ? html`<div class=\"page-section-actions\">\n <button @click=${add}><md-icon>add</md-icon>${i18next.t('button.add')}</button>\n </div>`\n : nothing}\n </header>\n ${rows.length\n ? html`<div class=\"entries\">${rows.map((row, index) => this.renderEntry(section, row, index, change, () => remove(index)))}</div>`\n : html`<div class=\"empty-slot\">\n <span class=\"muted\">${i18next.t(`text.employee-profile-why-${section.key}`)}</span>\n <button @click=${add}><md-icon>add</md-icon>${i18next.t('button.add')}</button>\n </div>`}\n </section>`\n }\n\n private renderEntry(section: ProfileSection, row: ProfileRow, index: number, change: (index: number, patch: ProfileRow) => void, remove: () => void) {\n const headline = headlineOf(section, row)\n const period = periodLabelOf(row, i18next.t('text.employee-profile-doc-ongoing'))\n const field = (one: ProfileField) => {\n const label = i18next.t(one.label)\n if (one.input === 'textarea') {\n return html`<label class=\"wide\"\n >${label}<textarea .value=${row[one.name] ?? ''} @input=${(e: any) => change(index, { [one.name]: e.target.value })}></textarea\n ></label>`\n }\n const ended = one.name === 'endedOn' && section.ongoing\n const input = html`<input\n type=${one.input ?? 'text'}\n .value=${ended && row.ongoing ? '' : (row[one.name] ?? '')}\n ?disabled=${ended && !!row.ongoing}\n @input=${(e: any) => change(index, { [one.name]: e.target.value })}\n />`\n /* A period begins a line of its own, its end beside it. */\n if (!ended) return html`<label class=${one.name === 'startedOn' ? 'start' : ''}>${label}${input}</label>`\n /* The end and 「going on」 share one cell - the period stays on one line. */\n return html`<div class=\"end\">\n <label>${label}${input}</label>\n <label class=\"ongoing\"\n ><input\n type=\"checkbox\"\n .checked=${!!row.ongoing}\n @change=${(e: any) => change(index, { ongoing: e.target.checked ? 'true' : '', ...(e.target.checked ? { endedOn: '' } : {}) })}\n />${i18next.t('label.employee-profile-ongoing')}</label\n >\n </div>`\n }\n\n return html`<div class=\"entry page-card\">\n <div class=\"entry-head\">\n <strong class=${headline ? '' : 'untitled'}>${headline || i18next.t('text.employee-profile-new-entry')}</strong>\n ${period ? html`<span class=\"period muted\">${period}</span>` : nothing}\n <button quiet icon-only title=${i18next.t('button.delete')} aria-label=${i18next.t('button.delete')} @click=${remove}><md-icon>delete</md-icon></button>\n </div>\n <div class=\"fields\">${section.fields.map(field)}</div>\n </div>`\n }\n}\n"]}
1
+ {"version":3,"file":"my-employee-profile-page.js","sourceRoot":"","sources":["../../../client/pages/employee-profile/my-employee-profile-page.ts"],"names":[],"mappings":"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","sourcesContent":["import '@material/web/icon/icon.js'\nimport '@operato/list-state/ox-empty-note.js'\nimport '@operato/list-state/ox-page-state.js'\n\nimport gql from 'graphql-tag'\nimport { css, html, nothing } from 'lit'\nimport { customElement } from 'lit/decorators.js'\n\nimport { MutationController, QueryController } from '@operato/graphql'\nimport { i18next, localize } from '@operato/i18n'\nimport { notify } from '@operato/layout'\nimport { PageView, DraftController } from '@operato/shell'\nimport { ButtonStyles, DocumentPageStyles, FormFieldStyles } from '@operato/styles'\nimport { getLanguages } from '@things-factory/auth-base/dist-client'\n\nimport {\n addSkills,\n blankRow,\n completenessOf,\n headlineOf,\n periodLabelOf,\n PROFILE_SECTIONS,\n profileFormOf,\n profileInputOf,\n type ProfileField,\n type ProfileForm,\n type ProfileRow,\n type ProfileSection\n} from './employee-profile-form'\nimport { readerLanguageOf, type Language } from './employee-profile-files'\nimport { openProfilePreview } from './employee-profile-preview'\n\nconst PROFILE_FIELDS = `\n id\n summary\n skills\n educations { school major degree startedOn endedOn }\n certificates { name issuer acquiredOn }\n careers { company department position startedOn endedOn duties }\n projects { name client startedOn endedOn role duties technologies }\n updatedAt\n`\n\nconst SUMMARY_LIMIT = 500\n\n/**\n * 내 프로필 — the employee writes their own career profile: a summary, skills, and the rows of education,\n * certificates, past jobs and projects; sales attaches it to quotations (ADR-0169 decisions 2 · 5). Saved whole.\n *\n * The page is built to be filled in: who it is about and how complete it is stand at the top, each missing essential a\n * click away; each row is a card whose headline reads like the document's line; a section with nothing yet says what\n * it is for; and the document itself is one click away (PDF), as sales will hand it in. An account not linked to an\n * employee record has no profile to write, and is told so before anything is typed.\n */\n@customElement('my-employee-profile-page')\nexport class MyEmployeeProfilePage extends localize(i18next)(PageView) {\n static styles = [\n FormFieldStyles,\n ButtonStyles,\n DocumentPageStyles,\n css`\n label {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-small, 4px);\n min-width: 0;\n font-size: var(--ui-t-small, 11.5px);\n color: var(--ui-muted, var(--md-sys-color-on-surface-variant));\n }\n label > :is(input, textarea) {\n font-size: var(--ui-t-body, 13px);\n color: var(--ui-ink, var(--md-sys-color-on-surface));\n }\n input,\n textarea {\n box-sizing: border-box;\n width: 100%;\n font-family: inherit;\n }\n input:not([type='checkbox']) {\n height: var(--form-element-height-medium, 32px);\n }\n textarea {\n min-height: 72px;\n resize: vertical;\n line-height: 1.5;\n }\n .muted {\n color: var(--ui-muted, var(--md-sys-color-on-surface-variant));\n }\n\n /* ── Who it is about, and how far along ─────────────────────────────── */\n .hero {\n display: grid;\n grid-template-columns: minmax(0, 1fr) minmax(240px, 340px);\n gap: var(--spacing-huge, 24px);\n align-items: center;\n }\n .who {\n display: flex;\n align-items: center;\n gap: var(--spacing-large, 12px);\n min-width: 0;\n }\n .avatar {\n flex: none;\n display: grid;\n place-items: center;\n width: 56px;\n height: 56px;\n border-radius: 50%;\n background: var(--md-sys-color-primary-container);\n color: var(--md-sys-color-on-primary-container);\n font-size: 22px;\n font-weight: 700;\n }\n .who h2 {\n margin: 0;\n font-size: var(--ui-t-headline, 20px);\n font-weight: 700;\n color: var(--ui-ink, var(--md-sys-color-on-surface));\n }\n .who p {\n margin: 2px 0 0;\n font-size: var(--ui-t-body, 13px);\n }\n .progress {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-small, 4px);\n }\n .progress-head {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n font-size: var(--ui-t-small, 11.5px);\n }\n .progress-head strong {\n font-size: var(--ui-t-title, 15px);\n color: var(--ui-ink, var(--md-sys-color-on-surface));\n }\n .bar {\n height: 8px;\n border-radius: 4px;\n background: var(--md-sys-color-surface-container-high);\n overflow: hidden;\n }\n .bar > span {\n display: block;\n height: 100%;\n border-radius: 4px;\n background: var(--md-sys-color-primary);\n transition: width 0.3s ease;\n }\n .missing {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--spacing-small, 4px);\n margin-top: var(--spacing-small, 4px);\n font-size: var(--ui-t-small, 11.5px);\n }\n .hero-foot {\n grid-column: 1 / -1;\n display: flex;\n align-items: center;\n gap: var(--spacing-medium, 8px);\n padding-top: var(--spacing-large, 12px);\n border-top: 1px solid var(--ui-line, var(--md-sys-color-outline-variant));\n font-size: var(--ui-t-small, 11.5px);\n }\n .hero-foot .unsaved {\n color: var(--md-sys-color-tertiary, var(--md-sys-color-primary));\n font-weight: 600;\n }\n .hero-foot button {\n margin-left: auto;\n }\n\n /* ── A section ─────────────────────────────────────────────────────── */\n .page-section-header h3 {\n display: flex;\n align-items: center;\n gap: var(--spacing-medium, 8px);\n }\n .page-section-header h3 md-icon {\n --md-icon-size: 20px;\n color: var(--md-sys-color-primary);\n }\n .count {\n min-width: 20px;\n padding: 1px 7px;\n border-radius: 10px;\n background: var(--md-sys-color-surface-container-high);\n font-size: var(--ui-t-small, 11.5px);\n font-weight: 600;\n text-align: center;\n color: var(--ui-muted, var(--md-sys-color-on-surface-variant));\n }\n .counter {\n margin-left: auto;\n font-size: var(--ui-t-small, 11.5px);\n }\n .summary textarea {\n min-height: 132px;\n }\n\n /* Skills as chips, the field to add more inside the same box. */\n .chips {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--spacing-small, 4px);\n min-height: 88px;\n align-content: flex-start;\n box-sizing: border-box;\n padding: var(--spacing-medium, 8px);\n border: 1px solid var(--md-sys-color-outline);\n border-radius: var(--input-field-border-radius, var(--md-sys-shape-corner-small, 4px));\n background: var(--md-sys-color-surface-container-highest);\n }\n .chips:focus-within {\n border-color: var(--md-sys-color-primary);\n }\n .chip {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n height: 26px;\n padding: 0 4px 0 10px;\n border-radius: 13px;\n background: var(--md-sys-color-secondary-container);\n color: var(--md-sys-color-on-secondary-container);\n font-size: var(--ui-t-body, 13px);\n }\n .chip button {\n height: 20px;\n width: 20px;\n padding: 0;\n border: 0;\n background: transparent;\n }\n .chip button md-icon {\n --md-icon-size: 14px;\n color: inherit;\n }\n .chips input {\n flex: 1 1 140px;\n width: auto;\n height: 26px;\n border: 0;\n padding: 0 4px;\n background: transparent;\n outline: none;\n }\n\n /* A row of a section is a card: its line as the document reads it, then its fields. */\n .entries {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-medium, 8px);\n }\n .entry {\n display: flex;\n flex-direction: column;\n gap: var(--spacing-large, 12px);\n }\n .entry-head {\n display: flex;\n align-items: center;\n gap: var(--spacing-medium, 8px);\n min-width: 0;\n }\n .entry-head strong {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: var(--ui-t-body, 13px);\n color: var(--ui-ink, var(--md-sys-color-on-surface));\n }\n .entry-head strong.untitled {\n font-weight: 400;\n color: var(--ui-muted, var(--md-sys-color-on-surface-variant));\n }\n .entry-head .period {\n flex: none;\n font-size: var(--ui-t-small, 11.5px);\n }\n .entry-head button {\n margin-left: auto;\n }\n .fields {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));\n gap: var(--spacing-medium, 8px) var(--spacing-large, 12px);\n }\n .fields .wide {\n grid-column: 1 / -1;\n }\n .fields .start {\n grid-column-start: 1;\n }\n .end {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n gap: var(--spacing-medium, 8px);\n align-items: end;\n min-width: 0;\n }\n .ongoing {\n flex-direction: row;\n align-items: center;\n gap: var(--spacing-small, 4px);\n height: var(--form-element-height-medium, 32px);\n font-size: var(--ui-t-body, 13px);\n white-space: nowrap;\n color: var(--ui-ink, var(--md-sys-color-on-surface));\n }\n .ongoing input {\n width: auto;\n }\n\n /* A section with nothing yet: what it is for, and the way to begin. */\n .empty-slot {\n display: flex;\n align-items: center;\n gap: var(--spacing-large, 12px);\n padding: var(--spacing-large, 12px) 16px;\n border: 1px dashed var(--md-sys-color-outline-variant);\n border-radius: var(--ui-r, 10px);\n font-size: var(--ui-t-body, 13px);\n }\n .empty-slot button {\n margin-left: auto;\n }\n\n @media (max-width: 720px) {\n .hero {\n grid-template-columns: 1fr;\n }\n .empty-slot {\n flex-direction: column;\n align-items: flex-start;\n }\n .empty-slot button {\n margin-left: 0;\n }\n }\n @media (pointer: coarse) {\n input,\n textarea {\n font-size: 16px;\n }\n }\n `\n ]\n\n /* Who the account is here and its profile (ADR-0190). */\n private profile = new QueryController(\n this,\n gql`\n query {\n myEmployees { id name jobPosition jobResponsibility hiredOn department { name } }\n myEmployeeProfile { ${PROFILE_FIELDS} }\n }\n `\n )\n\n /* Saving reads the profile again: the form is then the profile as saved. */\n private saving = new MutationController(\n this,\n gql`\n mutation ($profile: EmployeeProfileInput!) {\n saveMyEmployeeProfile(profile: $profile) { ${PROFILE_FIELDS} }\n }\n `,\n { refetch: () => [this.profile] }\n )\n\n /** What is written over the profile read; a save clears what it sent. */\n private typed = new DraftController<ProfileForm>(this, { read: () => this.held })\n\n /** The profile as last read or saved. */\n private get held(): ProfileForm {\n return profileFormOf(this.profile.data?.myEmployeeProfile)\n }\n\n get form(): ProfileForm {\n return { ...this.held, ...this.typed.edits }\n }\n\n /** Who the profile is about - the employee record's basics. */\n private get me(): any {\n return this.profile.data?.myEmployees?.[0] ?? null\n }\n\n /** Whether the account is linked to an employee record - unknown until read. */\n private get linked(): boolean | null {\n return this.profile.data ? !!this.me : null\n }\n\n private get updatedAt(): string | null {\n return this.profile.data?.myEmployeeProfile?.updatedAt ?? null\n }\n\n private get busy() {\n return this.saving.pending\n }\n\n private get dirty() {\n return JSON.stringify(profileInputOf(this.form)) !== JSON.stringify(profileInputOf(this.held))\n }\n\n get context() {\n return {\n title: i18next.t('title.my-employee-profile'),\n frame: 'document',\n actions: this.linked ? [{ title: i18next.t('button.save'), icon: 'save', emphasis: { raised: true }, action: () => this.save() }] : []\n }\n }\n\n private async save(): Promise<boolean> {\n if (this.busy) return false\n const profile = profileInputOf(this.form)\n if (!(await this.typed.submit(() => this.saving.run({ profile })))) return false\n notify({ message: i18next.t('text.saved') })\n return true\n }\n\n /** The document as sales will hand it in - what is written is saved first, so the preview shows it. */\n private async preview() {\n if (this.dirty && !(await this.save())) return\n const languages = ((await getLanguages()) ?? []) as Language[]\n openProfilePreview({ own: true }, languages, readerLanguageOf(languages))\n }\n\n private goTo(key: string) {\n const section = this.renderRoot.querySelector<HTMLElement>(`#${key}`)\n section?.scrollIntoView({ behavior: 'smooth', block: 'start' })\n section?.querySelector<HTMLElement>('textarea, input:not([type=\"checkbox\"])')?.focus({ preventScroll: true })\n }\n\n render() {\n return html`<ox-page-state .loading=${this.profile.loading && this.linked === null} .error=${this.linked === null ? (this.profile.error ?? null) : null} @retry=${() => this.profile.refetch()}>\n ${this.linked === false\n ? html`<ox-empty-note\n icon=\"badge\"\n title=${i18next.t('text.employee-profile-not-linked-title')}\n description=${i18next.t('text.employee-profile-not-linked-description')}\n ></ox-empty-note>`\n : this.linked\n ? html`<div class=\"page\">\n ${this.renderHero()}\n <div class=\"page-section-grid\">${this.renderSummary()} ${this.renderSkills()}</div>\n ${PROFILE_SECTIONS.map(section => this.renderSection(section))}\n </div>`\n : nothing}\n </ox-page-state>`\n }\n\n private renderHero() {\n const me = this.me ?? {}\n const { percent, missing } = completenessOf(this.form)\n const role = [me.jobPosition, me.jobResponsibility].filter(Boolean).join(' / ')\n const day = (value?: string | null) => (value ? String(value).slice(0, 10).replace(/-/g, '.') : '')\n\n return html`<section class=\"hero page-card\">\n <div class=\"who\">\n <div class=\"avatar\" aria-hidden=\"true\">${(me.name ?? '?').trim().slice(0, 1)}</div>\n <div>\n <h2>${me.name ?? ''}</h2>\n <p class=\"muted\">${[me.department?.name, role].filter(Boolean).join(' · ')}</p>\n ${me.hiredOn ? html`<p class=\"muted\">${i18next.t('label.hired-on')} ${day(me.hiredOn)}</p>` : nothing}\n </div>\n </div>\n\n <div class=\"progress\">\n <div class=\"progress-head\">\n <span class=\"muted\">${i18next.t('text.employee-profile-completeness')}</span>\n <strong>${percent}%</strong>\n </div>\n <div class=\"bar\" role=\"progressbar\" aria-valuemin=\"0\" aria-valuemax=\"100\" aria-valuenow=${percent}><span style=\"width:${percent}%\"></span></div>\n ${missing.length\n ? html`<div class=\"missing\">\n <span class=\"muted\">${i18next.t('text.employee-profile-to-fill')}</span>\n ${missing.map(key => html`<button small quiet @click=${() => this.goTo(key)}>${i18next.t(`label.employee-profile-${key}`)}</button>`)}\n </div>`\n : html`<div class=\"missing muted\">${i18next.t('text.employee-profile-ready')}</div>`}\n </div>\n\n <div class=\"hero-foot\">\n ${this.dirty\n ? html`<span class=\"unsaved\">${i18next.t('text.employee-profile-unsaved')}</span>`\n : this.updatedAt\n ? html`<span class=\"muted\">${i18next.t('label.employee-profile-updated-at')} ${day(this.updatedAt)}</span>`\n : nothing}\n <button ?disabled=${this.busy} @click=${() => this.preview()}><md-icon>preview</md-icon>${i18next.t('button.employee-profile-preview')}</button>\n </div>\n </section>`\n }\n\n private renderSummary() {\n const summary = this.form.summary\n return html`<section id=\"summary\" class=\"page-section summary\">\n <header class=\"page-section-header\">\n <h3><md-icon>person</md-icon>${i18next.t('label.employee-profile-summary')}</h3>\n <span class=\"counter muted\">${summary.length} / ${SUMMARY_LIMIT}</span>\n </header>\n <textarea\n aria-label=${i18next.t('label.employee-profile-summary')}\n maxlength=${SUMMARY_LIMIT}\n placeholder=${i18next.t('text.employee-profile-summary-example')}\n .value=${summary}\n @input=${(e: any) => this.typed.set('summary', e.target.value)}\n ></textarea>\n </section>`\n }\n\n private renderSkills() {\n const skills = this.form.skills\n const add = (input: HTMLInputElement) => {\n const next = addSkills(skills, input.value)\n if (next.length !== skills.length) this.typed.set('skills', next)\n input.value = ''\n }\n\n return html`<section id=\"skills\" class=\"page-section\">\n <header class=\"page-section-header\">\n <h3><md-icon>construction</md-icon>${i18next.t('label.employee-profile-skills')}</h3>\n ${skills.length ? html`<span class=\"count\">${skills.length}</span>` : nothing}\n </header>\n <div class=\"chips\" @click=${(e: Event) => (e.currentTarget as HTMLElement).querySelector('input')?.focus()}>\n ${skills.map(\n skill => html`<span class=\"chip\"\n >${skill}\n <button\n unstyled\n title=${i18next.t('button.delete')}\n aria-label=${`${i18next.t('button.delete')} ${skill}`}\n @click=${() => this.typed.set('skills', skills.filter(one => one !== skill))}\n >\n <md-icon>close</md-icon>\n </button></span\n >`\n )}\n <input\n aria-label=${i18next.t('label.employee-profile-skills')}\n placeholder=${i18next.t('text.employee-profile-skill-placeholder')}\n @keydown=${(e: KeyboardEvent) => {\n const input = e.target as HTMLInputElement\n if ((e.key === 'Enter' || e.key === ',') && !e.isComposing) {\n e.preventDefault()\n add(input)\n } else if (e.key === 'Backspace' && !input.value && skills.length) {\n this.typed.set('skills', skills.slice(0, -1))\n }\n }}\n @blur=${(e: Event) => add(e.target as HTMLInputElement)}\n />\n </div>\n </section>`\n }\n\n private renderSection(section: ProfileSection) {\n const rows = this.form[section.key]\n /* Each change reads the rows as they are now - two inputs before a render must not undo each other. */\n const current = () => this.form[section.key]\n const setRows = (next: ProfileRow[]) => this.typed.set(section.key, next)\n const change = (index: number, patch: ProfileRow) => setRows(current().map((row, at) => (at === index ? { ...row, ...patch } : row)))\n const remove = (index: number) => setRows(current().filter((_, at) => at !== index))\n /* The newest first, as the document is read; its first field takes the cursor. */\n const add = async () => {\n setRows([blankRow(section), ...current()])\n await this.updateComplete\n this.renderRoot.querySelector<HTMLInputElement>(`#${section.key} .entry input`)?.focus()\n }\n\n return html`<section id=${section.key} class=\"page-section\">\n <header class=\"page-section-header\">\n <h3><md-icon>${section.icon}</md-icon>${i18next.t(section.title)}</h3>\n ${rows.length ? html`<span class=\"count\">${rows.length}</span>` : nothing}\n ${rows.length\n ? html`<div class=\"page-section-actions\">\n <button @click=${add}><md-icon>add</md-icon>${i18next.t('button.add')}</button>\n </div>`\n : nothing}\n </header>\n ${rows.length\n ? html`<div class=\"entries\">${rows.map((row, index) => this.renderEntry(section, row, index, change, () => remove(index)))}</div>`\n : html`<div class=\"empty-slot\">\n <span class=\"muted\">${i18next.t(`text.employee-profile-why-${section.key}`)}</span>\n <button @click=${add}><md-icon>add</md-icon>${i18next.t('button.add')}</button>\n </div>`}\n </section>`\n }\n\n private renderEntry(section: ProfileSection, row: ProfileRow, index: number, change: (index: number, patch: ProfileRow) => void, remove: () => void) {\n const headline = headlineOf(section, row)\n const period = periodLabelOf(row, i18next.t('text.employee-profile-doc-ongoing'))\n const field = (one: ProfileField) => {\n const label = i18next.t(one.label)\n if (one.input === 'textarea') {\n return html`<label class=\"wide\"\n >${label}<textarea .value=${row[one.name] ?? ''} @input=${(e: any) => change(index, { [one.name]: e.target.value })}></textarea\n ></label>`\n }\n const ended = one.name === 'endedOn' && section.ongoing\n const input = html`<input\n type=${one.input ?? 'text'}\n .value=${ended && row.ongoing ? '' : (row[one.name] ?? '')}\n ?disabled=${ended && !!row.ongoing}\n @input=${(e: any) => change(index, { [one.name]: e.target.value })}\n />`\n /* A period begins a line of its own, its end beside it. */\n if (!ended) return html`<label class=${one.name === 'startedOn' ? 'start' : ''}>${label}${input}</label>`\n /* The end and 「going on」 share one cell - the period stays on one line. */\n return html`<div class=\"end\">\n <label>${label}${input}</label>\n <label class=\"ongoing\"\n ><input\n type=\"checkbox\"\n .checked=${!!row.ongoing}\n @change=${(e: any) => change(index, { ongoing: e.target.checked ? 'true' : '', ...(e.target.checked ? { endedOn: '' } : {}) })}\n />${i18next.t('label.employee-profile-ongoing')}</label\n >\n </div>`\n }\n\n return html`<div class=\"entry page-card\">\n <div class=\"entry-head\">\n <strong class=${headline ? '' : 'untitled'}>${headline || i18next.t('text.employee-profile-new-entry')}</strong>\n ${period ? html`<span class=\"period muted\">${period}</span>` : nothing}\n <button quiet icon-only title=${i18next.t('button.delete')} aria-label=${i18next.t('button.delete')} @click=${remove}><md-icon>delete</md-icon></button>\n </div>\n <div class=\"fields\">${section.fields.map(field)}</div>\n </div>`\n }\n}\n"]}