mirror of
https://github.com/ever-co/ever-gauzy.git
synced 2026-10-02 01:54:50 +08:00
Merge pull request #10255 from joel-kalema/feat/polish-key-result-dialogs
Feat/polish key result dialogs
This commit is contained in:
@@ -1,307 +1,219 @@
|
||||
<nb-card class="max-width-60vw">
|
||||
<nb-card-header class="d-flex flex-column">
|
||||
<span class="cancel"
|
||||
><i class="fas fa-times" (click)="closeDialog()"></i
|
||||
></span>
|
||||
<h4 class="title">
|
||||
{{
|
||||
(!!data
|
||||
? 'KEY_RESULT_PAGE.EDIT_KEY_RESULT'
|
||||
: 'KEY_RESULT_PAGE.ADD_KEY_RESULT'
|
||||
) | translate
|
||||
}}
|
||||
</h4>
|
||||
</nb-card-header>
|
||||
<nb-card-body>
|
||||
<form [formGroup]="keyResultsForm">
|
||||
<p
|
||||
class="helper-text"
|
||||
innerHtml="{{
|
||||
'KEY_RESULT_PAGE.HELPER_TEXT.KEY_RESULT_GENERAL' | translate
|
||||
}}"
|
||||
></p>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<label for="key-result-title" class="label">
|
||||
{{ 'FORM.LABELS.NAME' | translate }}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="key-result-title"
|
||||
nbInput
|
||||
fullWidth
|
||||
formControlName="name"
|
||||
placeholder="{{
|
||||
'KEY_RESULT_PAGE.FORM.PLACEHOLDERS.NAME' | translate
|
||||
}}"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<nb-card class="key-result-dialog">
|
||||
<nb-card-header class="header">
|
||||
<h4 class="title">
|
||||
{{ (!!data ? 'KEY_RESULT_PAGE.EDIT_KEY_RESULT' : 'KEY_RESULT_PAGE.ADD_KEY_RESULT') | translate }}
|
||||
</h4>
|
||||
<button type="button" class="cancel" [attr.aria-label]="'BUTTONS.CLOSE' | translate" (click)="closeDialog()">
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
</nb-card-header>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<label for="key-result-description" class="label mt-3">
|
||||
{{ 'FORM.LABELS.DESCRIPTION_OPTIONAL' | translate }}
|
||||
</label>
|
||||
<textarea
|
||||
id="key-result-description"
|
||||
nbInput
|
||||
fullWidth
|
||||
formControlName="description"
|
||||
placeholder="{{
|
||||
'KEY_RESULT_PAGE.FORM.PLACEHOLDERS.DESCRIPTION'
|
||||
| translate
|
||||
}}"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<nb-card-body>
|
||||
<!-- The form on a six-column grid with the help as a column beside it, as in the objective dialog: it
|
||||
used to run one field per row down 645px, with the general help as a paragraph on top. Hover and
|
||||
focus go to separate handlers, resolved together in the component, so leaving a field with the
|
||||
pointer does not wipe out the help the keyboard is still on. -->
|
||||
<div class="key-result-layout">
|
||||
<form class="key-result-form" [formGroup]="keyResultsForm">
|
||||
<div
|
||||
class="field span-6"
|
||||
(mouseenter)="hoverHelper('key-result-title')"
|
||||
(mouseleave)="hoverHelper('')"
|
||||
(focusin)="focusHelper('key-result-title')"
|
||||
(focusout)="focusHelper('')"
|
||||
>
|
||||
<label for="key-result-title" class="label">
|
||||
{{ 'FORM.LABELS.NAME' | translate }}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
id="key-result-title"
|
||||
nbInput
|
||||
fullWidth
|
||||
formControlName="name"
|
||||
placeholder="{{ 'KEY_RESULT_PAGE.FORM.PLACEHOLDERS.NAME' | translate }}"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ga-keyresult-type-select
|
||||
[parentFormGroup]="keyResultsForm"
|
||||
[numberUnits]="numberUnitsEnum"
|
||||
[orgId]="orgId"
|
||||
[settings]="settings"
|
||||
[KPIs]="KPIs"
|
||||
></ga-keyresult-type-select>
|
||||
<div class="field span-6">
|
||||
<label for="key-result-description" class="label">
|
||||
{{ 'FORM.LABELS.DESCRIPTION_OPTIONAL' | translate }}
|
||||
</label>
|
||||
<textarea
|
||||
id="key-result-description"
|
||||
nbInput
|
||||
fullWidth
|
||||
formControlName="description"
|
||||
placeholder="{{ 'KEY_RESULT_PAGE.FORM.PLACEHOLDERS.DESCRIPTION' | translate }}"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div
|
||||
class="col-md-7"
|
||||
(mouseenter)="helperText = 'key-result-owner'"
|
||||
(mouseleave)="helperText = ''"
|
||||
>
|
||||
<label for="key-result-owner" class="label mt-3">
|
||||
{{ 'KEY_RESULT_PAGE.FORM.LABELS.OWNER' | translate }}
|
||||
</label>
|
||||
<ga-employee-multi-select
|
||||
[multiple]="false"
|
||||
[allEmployees]="employees"
|
||||
[selectedEmployeeIds]="keyResultsForm.value.ownerId"
|
||||
[label]="false"
|
||||
(selectedChange)="selectEmployee($event, 'owner')"
|
||||
id="key-result-owner"
|
||||
placeholder="{{
|
||||
'KEY_RESULT_PAGE.FORM.LABELS.OWNER' | translate
|
||||
}}"
|
||||
class="header-selector employee-selector"
|
||||
></ga-employee-multi-select>
|
||||
</div>
|
||||
<div class="col-md-5 position-relative mt-3 helper-text">
|
||||
@if (helperText == 'key-result-owner') {
|
||||
<div
|
||||
class="mt-3 position-absolute"
|
||||
>
|
||||
<p>
|
||||
{{
|
||||
'KEY_RESULT_PAGE.HELPER_TEXT.KEY_RESULT_OWNER'
|
||||
| translate
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Type, and whatever that type needs: its fields are items of this grid. -->
|
||||
<ga-keyresult-type-select
|
||||
[parentFormGroup]="keyResultsForm"
|
||||
[numberUnits]="numberUnitsEnum"
|
||||
[orgId]="orgId"
|
||||
[settings]="settings"
|
||||
[KPIs]="KPIs"
|
||||
></ga-keyresult-type-select>
|
||||
|
||||
<div class="row">
|
||||
<div
|
||||
class="col-md-7"
|
||||
(mouseenter)="helperText = 'key-result-lead'"
|
||||
(mouseleave)="helperText = ''"
|
||||
>
|
||||
<label for="key-result-lead" class="label mt-3">
|
||||
{{ 'KEY_RESULT_PAGE.FORM.LABELS.LEAD' | translate }}
|
||||
</label>
|
||||
<ga-employee-multi-select
|
||||
[multiple]="false"
|
||||
[allEmployees]="employees"
|
||||
[selectedEmployeeIds]="keyResultsForm.value.leadId"
|
||||
[label]="false"
|
||||
(selectedChange)="selectEmployee($event, 'lead')"
|
||||
id="key-result-lead"
|
||||
placeholder="{{
|
||||
'KEY_RESULT_PAGE.FORM.LABELS.LEAD' | translate
|
||||
}}"
|
||||
class="header-selector employee-selector"
|
||||
></ga-employee-multi-select>
|
||||
</div>
|
||||
<div class="col-md-5 position-relative mt-3 helper-text">
|
||||
@if (helperText == 'key-result-lead') {
|
||||
<div
|
||||
class="mt-3 helper-text position-absolute"
|
||||
>
|
||||
<p>
|
||||
{{
|
||||
'KEY_RESULT_PAGE.HELPER_TEXT.KEY_RESULT_LEAD'
|
||||
| translate
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="field span-3"
|
||||
(mouseenter)="hoverHelper('key-result-owner')"
|
||||
(mouseleave)="hoverHelper('')"
|
||||
(focusin)="focusHelper('key-result-owner')"
|
||||
(focusout)="focusHelper('')"
|
||||
>
|
||||
<label for="key-result-owner" class="label">
|
||||
{{ 'KEY_RESULT_PAGE.FORM.LABELS.OWNER' | translate }}
|
||||
</label>
|
||||
<ga-employee-multi-select
|
||||
[multiple]="false"
|
||||
[allEmployees]="employees"
|
||||
[selectedEmployeeIds]="keyResultsForm.value.ownerId"
|
||||
[label]="false"
|
||||
(selectedChange)="selectEmployee($event, 'owner')"
|
||||
id="key-result-owner"
|
||||
placeholder="{{ 'KEY_RESULT_PAGE.FORM.LABELS.OWNER' | translate }}"
|
||||
class="header-selector employee-selector"
|
||||
></ga-employee-multi-select>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-7">
|
||||
<label for="key-result-deadline" class="label mt-3">
|
||||
{{ 'KEY_RESULT_PAGE.FORM.LABELS.DEADLINE' | translate }}
|
||||
</label>
|
||||
<nb-select
|
||||
(selectedChange)="deadlineValidators()"
|
||||
id="key-result-deadline"
|
||||
fullWidth
|
||||
formControlName="deadline"
|
||||
>
|
||||
@for (
|
||||
deadline of keyResultDeadlineEnum | keyvalue
|
||||
; track
|
||||
deadline) {
|
||||
<nb-option
|
||||
[value]="deadline.value"
|
||||
>
|
||||
{{
|
||||
'KEY_RESULT_PAGE.DEADLINE.' + deadline.key
|
||||
| translate
|
||||
}}
|
||||
</nb-option>
|
||||
}
|
||||
</nb-select>
|
||||
</div>
|
||||
<div class="col-md-5 position-relative mt-3"></div>
|
||||
</div>
|
||||
<div
|
||||
class="field span-3"
|
||||
(mouseenter)="hoverHelper('key-result-lead')"
|
||||
(mouseleave)="hoverHelper('')"
|
||||
(focusin)="focusHelper('key-result-lead')"
|
||||
(focusout)="focusHelper('')"
|
||||
>
|
||||
<label for="key-result-lead" class="label">
|
||||
{{ 'KEY_RESULT_PAGE.FORM.LABELS.LEAD' | translate }}
|
||||
</label>
|
||||
<ga-employee-multi-select
|
||||
[multiple]="false"
|
||||
[allEmployees]="employees"
|
||||
[selectedEmployeeIds]="keyResultsForm.value.leadId"
|
||||
[label]="false"
|
||||
(selectedChange)="selectEmployee($event, 'lead')"
|
||||
id="key-result-lead"
|
||||
placeholder="{{ 'KEY_RESULT_PAGE.FORM.LABELS.LEAD' | translate }}"
|
||||
class="header-selector employee-selector"
|
||||
></ga-employee-multi-select>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-7">
|
||||
<div class="row mt-3">
|
||||
@if (
|
||||
keyResultsForm.value.deadline ==
|
||||
keyResultDeadlineEnum.HARD_AND_SOFT_DEADLINE
|
||||
) {
|
||||
<div
|
||||
class="col-md-6"
|
||||
>
|
||||
<label for="soft-deadline" class="label">
|
||||
{{
|
||||
'KEY_RESULT_PAGE.FORM.LABELS.SOFT_DEADLINE'
|
||||
| translate
|
||||
}}
|
||||
</label>
|
||||
<input
|
||||
nbInput
|
||||
id="soft-deadline"
|
||||
fullWidth
|
||||
placeholder="{{
|
||||
'KEY_RESULT_PAGE.FORM.LABELS.SOFT_DEADLINE'
|
||||
| translate
|
||||
}}"
|
||||
[nbDatepicker]="softDeadlinePicker"
|
||||
formControlName="softDeadline"
|
||||
/>
|
||||
<nb-datepicker
|
||||
#softDeadlinePicker
|
||||
[min]="minDate"
|
||||
[max]="
|
||||
keyResultsForm.value.hardDeadline
|
||||
? keyResultsForm.value.hardDeadline
|
||||
: null
|
||||
"
|
||||
></nb-datepicker>
|
||||
</div>
|
||||
}
|
||||
@if (
|
||||
keyResultsForm.value.deadline ==
|
||||
keyResultDeadlineEnum.HARD_AND_SOFT_DEADLINE ||
|
||||
keyResultsForm.value.deadline ==
|
||||
keyResultDeadlineEnum.HARD_DEADLINE
|
||||
) {
|
||||
<div
|
||||
class="col-md-6"
|
||||
>
|
||||
<label for="hard-deadline" class="label">
|
||||
{{
|
||||
'KEY_RESULT_PAGE.FORM.LABELS.HARD_DEADLINE'
|
||||
| translate
|
||||
}}
|
||||
</label>
|
||||
<input
|
||||
nbInput
|
||||
id="hard-deadline"
|
||||
fullWidth
|
||||
placeholder="{{
|
||||
'KEY_RESULT_PAGE.FORM.LABELS.HARD_DEADLINE'
|
||||
| translate
|
||||
}}"
|
||||
[nbDatepicker]="hardDeadlinePicker"
|
||||
formControlName="hardDeadline"
|
||||
/>
|
||||
<nb-datepicker
|
||||
#hardDeadlinePicker
|
||||
[min]="
|
||||
keyResultsForm.value.softDeadline
|
||||
? keyResultsForm.value.softDeadline
|
||||
: minDate
|
||||
"
|
||||
></nb-datepicker>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-5 position-relative mt-3"></div>
|
||||
</div>
|
||||
<div class="field span-3">
|
||||
<label for="key-result-deadline" class="label">
|
||||
{{ 'KEY_RESULT_PAGE.FORM.LABELS.DEADLINE' | translate }}
|
||||
</label>
|
||||
<nb-select
|
||||
(selectedChange)="deadlineValidators()"
|
||||
id="key-result-deadline"
|
||||
fullWidth
|
||||
formControlName="deadline"
|
||||
>
|
||||
@for (deadline of keyResultDeadlineEnum | keyvalue; track deadline) {
|
||||
<nb-option [value]="deadline.value">
|
||||
{{ 'KEY_RESULT_PAGE.DEADLINE.' + deadline.key | translate }}
|
||||
</nb-option>
|
||||
}
|
||||
</nb-select>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-7">
|
||||
<div class="row mt-3">
|
||||
<div class="col-12">
|
||||
<div class="d-flex flex-row align-items-start">
|
||||
<nb-toggle
|
||||
id="updated-value"
|
||||
formControlName="assignAsObjective"
|
||||
status="primary"
|
||||
>
|
||||
</nb-toggle>
|
||||
<span class="mr-3 ml-3">{{
|
||||
'KEY_RESULT_PAGE.FORM.LABELS.ASSIGN_AS_OBJECTIVE'
|
||||
| translate
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-5 position-relative mt-3"></div>
|
||||
</div>
|
||||
<ga-goal-level-select
|
||||
[parentFormGroup]="keyResultsForm"
|
||||
[orgId]="orgId"
|
||||
[teams]="teams"
|
||||
[hideOrg]="hideOrg"
|
||||
[hideEmployee]="hideEmployee"
|
||||
[hideTeam]="hideTeam"
|
||||
[helperText]="helperText"
|
||||
[employees]="employees"
|
||||
[orgName]="orgName"
|
||||
[alignedGoal]="true"
|
||||
[enableHelperText]="true"
|
||||
>
|
||||
</ga-goal-level-select>
|
||||
</form>
|
||||
</nb-card-body>
|
||||
<nb-card-footer class="text-left">
|
||||
<button
|
||||
status="basic"
|
||||
outline
|
||||
class="mr-2"
|
||||
nbButton
|
||||
(click)="closeDialog()"
|
||||
>
|
||||
{{ 'BUTTONS.CANCEL' | translate }}
|
||||
</button>
|
||||
<button
|
||||
[disabled]="keyResultsForm.invalid"
|
||||
nbButton
|
||||
status="success"
|
||||
(click)="saveKeyResult()"
|
||||
>
|
||||
{{ 'BUTTONS.SAVE' | translate }}
|
||||
</button>
|
||||
</nb-card-footer>
|
||||
</nb-card>
|
||||
@if (keyResultsForm.value.deadline == keyResultDeadlineEnum.HARD_AND_SOFT_DEADLINE) {
|
||||
<div class="field span-3">
|
||||
<label for="soft-deadline" class="label">
|
||||
{{ 'KEY_RESULT_PAGE.FORM.LABELS.SOFT_DEADLINE' | translate }}
|
||||
</label>
|
||||
<input
|
||||
nbInput
|
||||
id="soft-deadline"
|
||||
fullWidth
|
||||
placeholder="{{ 'KEY_RESULT_PAGE.FORM.LABELS.SOFT_DEADLINE' | translate }}"
|
||||
[nbDatepicker]="softDeadlinePicker"
|
||||
formControlName="softDeadline"
|
||||
/>
|
||||
<nb-datepicker
|
||||
#softDeadlinePicker
|
||||
[min]="minDate"
|
||||
[max]="keyResultsForm.value.hardDeadline ? keyResultsForm.value.hardDeadline : null"
|
||||
></nb-datepicker>
|
||||
</div>
|
||||
} @if (keyResultsForm.value.deadline == keyResultDeadlineEnum.HARD_AND_SOFT_DEADLINE ||
|
||||
keyResultsForm.value.deadline == keyResultDeadlineEnum.HARD_DEADLINE) {
|
||||
<div class="field span-3">
|
||||
<label for="hard-deadline" class="label">
|
||||
{{ 'KEY_RESULT_PAGE.FORM.LABELS.HARD_DEADLINE' | translate }}
|
||||
</label>
|
||||
<input
|
||||
nbInput
|
||||
id="hard-deadline"
|
||||
fullWidth
|
||||
placeholder="{{ 'KEY_RESULT_PAGE.FORM.LABELS.HARD_DEADLINE' | translate }}"
|
||||
[nbDatepicker]="hardDeadlinePicker"
|
||||
formControlName="hardDeadline"
|
||||
/>
|
||||
<nb-datepicker
|
||||
#hardDeadlinePicker
|
||||
[min]="keyResultsForm.value.softDeadline ? keyResultsForm.value.softDeadline : minDate"
|
||||
></nb-datepicker>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Turning this on adds the objective fields below, so it reads as the switch it is. -->
|
||||
<div class="field span-6 toggle-field">
|
||||
<!-- The label is projected into nb-toggle, so it sits inside the control's own
|
||||
<label> and names the switch for a screen reader. -->
|
||||
<nb-toggle id="updated-value" formControlName="assignAsObjective" status="primary">
|
||||
{{ 'KEY_RESULT_PAGE.FORM.LABELS.ASSIGN_AS_OBJECTIVE' | translate }}
|
||||
</nb-toggle>
|
||||
</div>
|
||||
|
||||
<ga-goal-level-select
|
||||
[parentFormGroup]="keyResultsForm"
|
||||
[orgId]="orgId"
|
||||
[teams]="teams"
|
||||
[hideOrg]="hideOrg"
|
||||
[hideEmployee]="hideEmployee"
|
||||
[hideTeam]="hideTeam"
|
||||
[employees]="employees"
|
||||
[orgName]="orgName"
|
||||
[alignedGoal]="true"
|
||||
[enableHelperText]="false"
|
||||
(helperTextChange)="levelHelper($event)"
|
||||
>
|
||||
</ga-goal-level-select>
|
||||
</form>
|
||||
|
||||
<!-- Follows the pointer or the keyboard focus from field to field, and falls back to what a Key
|
||||
Result is. A polite live region, so a screen reader reads the new help as focus moves. -->
|
||||
<aside class="key-result-help" aria-live="polite" aria-atomic="true">
|
||||
@switch (helperText) { @case ('key-result-owner') {
|
||||
<p>{{ 'KEY_RESULT_PAGE.HELPER_TEXT.KEY_RESULT_OWNER' | translate }}</p>
|
||||
} @case ('key-result-lead') {
|
||||
<p>{{ 'KEY_RESULT_PAGE.HELPER_TEXT.KEY_RESULT_LEAD' | translate }}</p>
|
||||
} @case ('objective-level') {
|
||||
<p>{{ 'GOALS_PAGE.HELPER_TEXT.OBJECTIVE_LEVEL' | translate }}</p>
|
||||
} @case ('objective-owner') {
|
||||
<p>{{ 'GOALS_PAGE.HELPER_TEXT.OBJECTIVE_OWNER' | translate }}</p>
|
||||
} @default {
|
||||
<p [innerHTML]="'KEY_RESULT_PAGE.HELPER_TEXT.KEY_RESULT_GENERAL' | translate"></p>
|
||||
} }
|
||||
</aside>
|
||||
</div>
|
||||
</nb-card-body>
|
||||
|
||||
<nb-card-footer class="footer">
|
||||
<button nbButton ghost status="basic" size="small" (click)="closeDialog()">
|
||||
{{ 'BUTTONS.CANCEL' | translate }}
|
||||
</button>
|
||||
<button
|
||||
[disabled]="keyResultsForm.invalid"
|
||||
nbButton
|
||||
status="success"
|
||||
size="small"
|
||||
(click)="saveKeyResult()"
|
||||
>
|
||||
{{ 'BUTTONS.SAVE' | translate }}
|
||||
</button>
|
||||
</nb-card-footer>
|
||||
</nb-card>
|
||||
|
||||
@@ -1,53 +1,336 @@
|
||||
@use 'gauzy/_gauzy-dialogs' as *;
|
||||
|
||||
.max-width-60vw {
|
||||
width: 645px;
|
||||
}
|
||||
// The Add / Edit Key Result dialog, on the same scale as the objective and KPI
|
||||
// dialogs: a header row, 12px labels over 2rem controls on a six-column grid,
|
||||
// the hover / focus help as a quiet column beside the form, and a footer whose
|
||||
// buttons sit on the trailing edge. Every custom property carries a fallback —
|
||||
// `--gauzy-*` is undefined in material-light / material-dark, where a `var()`
|
||||
// without one is dropped.
|
||||
$control-height: 2rem;
|
||||
$control-font-size: 12px;
|
||||
$control-padding-x: 0.75rem;
|
||||
$inset: 1rem;
|
||||
$divider: 1px solid var(--gauzy-border-default-color, rgba(126, 126, 143, 0.18));
|
||||
$muted: var(--gauzy-text-color-2, rgba(126, 126, 143, 1));
|
||||
// Below this the six columns leave the paired fields too narrow beside the
|
||||
// 14rem help column, so the help moves under the form.
|
||||
$stack-below: 991.98px;
|
||||
// Below this the form itself goes to one column.
|
||||
$single-column-below: 575.98px;
|
||||
// 2 x 1rem body padding + a 36rem form + the 1rem layout gap + the 14rem help.
|
||||
$side-by-side-min: 53rem;
|
||||
|
||||
.helper-text {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
letter-spacing: 0em;
|
||||
text-align: left;
|
||||
// Was a fixed 645px, one field per row, which ran the form 830px down the page.
|
||||
.key-result-dialog {
|
||||
width: clamp($side-by-side-min, 60vw, 58rem);
|
||||
|
||||
p {
|
||||
padding-right: 5px;
|
||||
@media (max-width: $stack-below) {
|
||||
width: min(calc(100vw - 2rem), 40rem);
|
||||
}
|
||||
}
|
||||
|
||||
// `--gauzy-card-1` / `--gauzy-card-2` are undefined in material-light and
|
||||
// material-dark: `themes.scss` registers those two against Nebular's own parent
|
||||
// themes, which carry no `gauzy-card-*` keys. A `var()` with no fallback makes
|
||||
// the browser throw the entire declaration away, so both uses below carry one.
|
||||
// This stylesheet is shared — `key-result-parameters` points its `styleUrls`
|
||||
// here and `edit-objective` / `goal-details` `@forward` it — so the fix reaches
|
||||
// all four dialogs.
|
||||
:host {
|
||||
nb-card {
|
||||
background: var(--gauzy-card-1, var(--card-background-color));
|
||||
// Fallback required: `--gauzy-card-1` is undefined in material-light and
|
||||
// material-dark, and a `var()` with no fallback drops the declaration.
|
||||
background-color: var(--gauzy-card-1, var(--card-background-color));
|
||||
}
|
||||
nb-tab,
|
||||
.content {
|
||||
background-color: var(--gauzy-card-2, rgba(126, 126, 143, 0.08));
|
||||
}
|
||||
nb-tab {
|
||||
height: 20.75vh;
|
||||
}
|
||||
::ng-deep {
|
||||
nb-progress-bar {
|
||||
width: 100%;
|
||||
.progress-container {
|
||||
height: 10px !important;
|
||||
}
|
||||
.progress-value {
|
||||
span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Title and close on one line; the close used to sit stacked above the title.
|
||||
nb-card-header.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.75rem;
|
||||
padding: 0.875rem $inset;
|
||||
border-bottom: $divider;
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
font-size: $control-font-size;
|
||||
font-weight: 600;
|
||||
line-height: 1rem;
|
||||
color: var(--gauzy-text-color-1, inherit);
|
||||
}
|
||||
|
||||
// A real button, so the close is reachable from the keyboard and named for
|
||||
// a screen reader; the browser's button chrome is stripped so it still
|
||||
// reads as the bare icon it was.
|
||||
.cancel {
|
||||
width: auto;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: none;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid nb-theme(color-primary-default);
|
||||
outline-offset: 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.info {
|
||||
color: nb-theme(color-info-default);
|
||||
|
||||
nb-card-body {
|
||||
padding: $inset;
|
||||
}
|
||||
|
||||
.key-result-layout {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: $inset;
|
||||
|
||||
@media (max-width: $stack-below) {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
}
|
||||
|
||||
// Name and Description across the grid, then the type's own fields, the two
|
||||
// people, the deadlines, and the objective switch.
|
||||
.key-result-form {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, minmax(0, 1fr));
|
||||
gap: 0.875rem 0.75rem;
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
align-content: start;
|
||||
|
||||
@media (max-width: $single-column-below) {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.field {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.span-2 {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.span-3 {
|
||||
grid-column: span 3;
|
||||
|
||||
// One column below this, so three of them would create implicit columns and
|
||||
// leave these fields narrow instead of filling the row.
|
||||
@media (max-width: $single-column-below) {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
.span-6 {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
// The type picker and the aligned-objective picker each render their fields
|
||||
// inside wrappers of their own. Taking the wrappers out of the box tree makes
|
||||
// those fields items of this grid, so they pair up with everything else
|
||||
// instead of stacking one per row.
|
||||
::ng-deep ga-keyresult-type-select,
|
||||
::ng-deep ga-keyresult-type-select > div,
|
||||
::ng-deep ga-goal-level-select,
|
||||
::ng-deep ga-goal-level-select > div {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
::ng-deep .kr-field,
|
||||
::ng-deep .goal-level-field {
|
||||
min-width: 0;
|
||||
grid-column: span 3;
|
||||
|
||||
@media (max-width: $single-column-below) {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Initial value, target value and unit read as one trio, so the first of
|
||||
// them starts a row of its own rather than pairing with Type and leaving the
|
||||
// other two on the next line. A task's project and task pair up the same way.
|
||||
::ng-deep .kr-field-initial {
|
||||
grid-column: 1 / span 2;
|
||||
}
|
||||
|
||||
::ng-deep .kr-field-target,
|
||||
::ng-deep .kr-field-unit {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
::ng-deep .kr-field-project {
|
||||
grid-column: 1 / span 3;
|
||||
}
|
||||
|
||||
@media (max-width: $single-column-below) {
|
||||
::ng-deep .kr-field-initial,
|
||||
::ng-deep .kr-field-target,
|
||||
::ng-deep .kr-field-unit,
|
||||
::ng-deep .kr-field-project {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
// The switch and its label on one line, with the rule that separates the key
|
||||
// result from the objective it can also become.
|
||||
.toggle-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 0.25rem;
|
||||
padding-top: 0.875rem;
|
||||
border-top: $divider;
|
||||
}
|
||||
|
||||
// The label now lives inside nb-toggle, where `status="primary"` would
|
||||
// otherwise paint it with the primary colour at the toggle's own text size.
|
||||
.toggle-field ::ng-deep nb-toggle .text {
|
||||
padding-inline-start: 0.625rem;
|
||||
font-size: $control-font-size;
|
||||
font-weight: 600;
|
||||
line-height: 1rem;
|
||||
color: var(--gauzy-text-color-1, inherit);
|
||||
}
|
||||
|
||||
// A note beside the form, not a second column of body copy.
|
||||
.key-result-help {
|
||||
flex: 0 0 14rem;
|
||||
align-self: stretch;
|
||||
padding-inline-start: $inset;
|
||||
border-inline-start: $divider;
|
||||
font-size: $control-font-size;
|
||||
line-height: 1.125rem;
|
||||
color: $muted;
|
||||
|
||||
// `inherit` against the theme's paragraph type, which would set the help at
|
||||
// 14px inside a 12px column.
|
||||
::ng-deep p {
|
||||
margin: 0;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
::ng-deep b {
|
||||
color: var(--gauzy-text-color-1, inherit);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
// Under the form the hairline moves to the top edge; `flex: none` so the
|
||||
// 14rem basis does not turn into a height in the column.
|
||||
@media (max-width: $stack-below) {
|
||||
flex: none;
|
||||
padding-inline-start: 0;
|
||||
padding-top: $inset;
|
||||
border-inline-start: 0;
|
||||
border-top: $divider;
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep label.label {
|
||||
display: block;
|
||||
margin: 0 0 0.375rem;
|
||||
font-size: $control-font-size;
|
||||
font-weight: 600;
|
||||
line-height: 1rem;
|
||||
color: $muted;
|
||||
}
|
||||
|
||||
// Labels inside the two pickers carry bootstrap's `.mt-3`, 1rem with
|
||||
// `!important`; on this grid the row gap sets the rhythm.
|
||||
::ng-deep label.label.mt-3 {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
input[nbInput],
|
||||
textarea[nbInput] {
|
||||
padding: 0.375rem $control-padding-x;
|
||||
font-size: $control-font-size;
|
||||
line-height: 1rem;
|
||||
}
|
||||
|
||||
input[nbInput],
|
||||
::ng-deep input[nbInput] {
|
||||
height: $control-height;
|
||||
// `!important` against the shared `dialog()` mixin (`gauzy/_gauzy-dialogs`),
|
||||
// which pins every input and select in a dialog to a 2.375rem `min-height`
|
||||
// with `!important` of its own.
|
||||
min-height: $control-height !important;
|
||||
padding: 0 $control-padding-x;
|
||||
font-size: $control-font-size;
|
||||
}
|
||||
|
||||
textarea[nbInput] {
|
||||
display: block;
|
||||
height: 4rem;
|
||||
min-height: 4rem !important;
|
||||
}
|
||||
|
||||
// Both selectors on purpose: the `dialog()` mixin pins
|
||||
// `nb-select.appearance-outline.status-basic .select-button` at 2.375rem with
|
||||
// `!important`, so only a selector of its own shape can win, while the bare
|
||||
// one still reaches a select carrying another status.
|
||||
::ng-deep nb-select .select-button,
|
||||
::ng-deep nb-select.appearance-outline.status-basic .select-button {
|
||||
height: $control-height;
|
||||
min-height: $control-height !important;
|
||||
padding-block: 0;
|
||||
padding-inline-start: $control-padding-x !important;
|
||||
font-size: $control-font-size;
|
||||
line-height: 1rem;
|
||||
}
|
||||
|
||||
// The project and task pickers are `ng-select`s, not `nb-select`s.
|
||||
::ng-deep .ng-select .ng-select-container {
|
||||
min-height: $control-height;
|
||||
font-size: $control-font-size;
|
||||
}
|
||||
|
||||
// Where a KPI select goes once a KPI exists: a dashed slot the height of a
|
||||
// control, rather than a filled primary button in the middle of the form.
|
||||
::ng-deep .add-inline {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.375rem;
|
||||
width: 100%;
|
||||
height: $control-height;
|
||||
margin: 0;
|
||||
padding: 0 $control-padding-x;
|
||||
border: 1px dashed var(--gauzy-border-default-color, rgba(126, 126, 143, 0.4));
|
||||
border-radius: var(--gauzy-radius-sm, 0.375rem);
|
||||
background: transparent;
|
||||
font-size: $control-font-size;
|
||||
font-weight: 600;
|
||||
line-height: 1rem;
|
||||
color: nb-theme(color-primary-default);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.15s ease, border-color 0.15s ease;
|
||||
|
||||
i {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: nb-theme(color-primary-default);
|
||||
background-color: var(--gauzy-hover-tint, rgba(126, 126, 143, 0.12));
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid nb-theme(color-primary-default);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
// Buttons on the trailing edge, Cancel quiet beside the primary action.
|
||||
nb-card-footer.footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem $inset;
|
||||
border-top: $divider;
|
||||
|
||||
button {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,14 @@ export class EditKeyResultsComponent implements OnInit, OnDestroy {
|
||||
orgName: string;
|
||||
numberUnitsEnum: string[] = Object.values(KeyResultNumberUnitsEnum);
|
||||
helperText = '';
|
||||
/**
|
||||
* The field the pointer is over, the field holding keyboard focus, and the field the level picker reports
|
||||
* (it resolves its own hover and focus before emitting). Tracked apart: held in one value, ending any one
|
||||
* interaction cleared the help while another was still on a field.
|
||||
*/
|
||||
private hoveredField = '';
|
||||
private focusedField = '';
|
||||
private levelField = '';
|
||||
teams: IOrganizationTeam[] = [];
|
||||
hideOrg = false;
|
||||
hideTeam = false;
|
||||
@@ -162,6 +170,33 @@ export class EditKeyResultsComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
/** Records the field the pointer moved onto, or `''` when it left one. */
|
||||
hoverHelper(field: string) {
|
||||
this.hoveredField = field;
|
||||
this.showHelper();
|
||||
}
|
||||
|
||||
/** Records the field that took keyboard focus, or `''` when it lost it. */
|
||||
focusHelper(field: string) {
|
||||
this.focusedField = field;
|
||||
this.showHelper();
|
||||
}
|
||||
|
||||
/** Records whichever of Level or Owner the aligned-objective picker is reporting, or `''` for none. */
|
||||
levelHelper(field: string) {
|
||||
this.levelField = field;
|
||||
this.showHelper();
|
||||
}
|
||||
|
||||
/**
|
||||
* Picks the help the aside shows. The pointer wins while it is over a field, so hovering one field while
|
||||
* another holds focus still reads as before; the help falls back to whatever is left and only clears once
|
||||
* every interaction has ended.
|
||||
*/
|
||||
private showHelper() {
|
||||
this.helperText = this.hoveredField || this.levelField || this.focusedField;
|
||||
}
|
||||
|
||||
selectEmployee(event, control) {
|
||||
if (control === 'lead') {
|
||||
this.keyResultsForm.patchValue({ leadId: event });
|
||||
|
||||
+66
-94
@@ -1,94 +1,66 @@
|
||||
<nb-card style="width: 645px; height: 390px">
|
||||
<nb-card-header>
|
||||
<span class="cancel"
|
||||
><i class="fas fa-times" (click)="closeDialog(null)"></i
|
||||
></span>
|
||||
<h4 class="title">{{
|
||||
'KEY_RESULT_PAGE.EDIT_KEY_RESULT_PARAMETERS'
|
||||
| translate
|
||||
}}</h4>
|
||||
</nb-card-header>
|
||||
<nb-card-body class="p-0">
|
||||
<nb-tabset>
|
||||
<nb-tab tabTitle="{{ 'KEY_RESULT_PAGE.FORM.LABELS.WEIGHT' | translate }}">
|
||||
<p class="info">
|
||||
{{ 'KEY_RESULT_PAGE.WEIGHT.MESSAGE' | translate }}
|
||||
</p>
|
||||
<form [formGroup]="weightForm">
|
||||
<div class="row mt-2">
|
||||
<div class="col-md-6">
|
||||
<label for="key-result-weight" class="label mt-3">
|
||||
{{
|
||||
'KEY_RESULT_PAGE.FORM.LABELS.WEIGHT'
|
||||
| translate
|
||||
}}
|
||||
</label>
|
||||
<nb-select
|
||||
id="key-result-weight"
|
||||
fullWidth
|
||||
formControlName="weight"
|
||||
>
|
||||
@for (
|
||||
weight of keyResultWeightEnum
|
||||
| keyvalue
|
||||
; track
|
||||
weight) {
|
||||
<nb-option
|
||||
[value]="weight.value"
|
||||
>{{
|
||||
'KEY_RESULT_PAGE.WEIGHT.' + weight.key
|
||||
| translate
|
||||
}}</nb-option
|
||||
>
|
||||
}
|
||||
</nb-select>
|
||||
</div>
|
||||
<div
|
||||
class="col-md-6 d-flex flex-column justify-content-around"
|
||||
>
|
||||
<label
|
||||
for="goal-progress-comparison"
|
||||
class="label mt-3"
|
||||
>
|
||||
{{
|
||||
'KEY_RESULT_PAGE.WEIGHT.OBJECTIVE_PROGRESS'
|
||||
| translate: { weight: keyResultWeight }
|
||||
}}
|
||||
</label>
|
||||
<span class="w-100 d-flex align-items-center">
|
||||
<span class="mr-3">{{ keyResultWeight }}%</span>
|
||||
<nb-progress-bar
|
||||
id="goal-progress-comparison"
|
||||
[value]="keyResultWeight"
|
||||
status="primary"
|
||||
size="small"
|
||||
>
|
||||
</nb-progress-bar>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</nb-tab>
|
||||
<nb-tab tabTitle="{{ 'KEY_RESULT_PAGE.FORM.LABELS.TYPE' | translate }}">
|
||||
<form [formGroup]="typeForm">
|
||||
<ga-keyresult-type-select
|
||||
[enableHelperText]="false"
|
||||
[parentFormGroup]="typeForm"
|
||||
[orgId]="data.orgId"
|
||||
[KPIs]="KPIs"
|
||||
[settings]="data.settings"
|
||||
[numberUnits]="numberUnitsEnum"
|
||||
></ga-keyresult-type-select>
|
||||
</form>
|
||||
</nb-tab>
|
||||
</nb-tabset>
|
||||
</nb-card-body>
|
||||
<nb-card-footer class="text-left content">
|
||||
<button class="mr-3" outline nbButton (click)="closeDialog(null)">
|
||||
{{ 'BUTTONS.CANCEL' | translate }}
|
||||
</button>
|
||||
<button nbButton status="success" (click)="updateKeyResult()">
|
||||
{{ 'BUTTONS.SAVE' | translate }}
|
||||
</button>
|
||||
</nb-card-footer>
|
||||
</nb-card>
|
||||
<nb-card class="parameters-dialog">
|
||||
<nb-card-header class="header">
|
||||
<h4 class="title">{{ 'KEY_RESULT_PAGE.EDIT_KEY_RESULT_PARAMETERS' | translate }}</h4>
|
||||
<button type="button" class="cancel" [attr.aria-label]="'BUTTONS.CLOSE' | translate" (click)="closeDialog(null)">
|
||||
<i class="fas fa-times" aria-hidden="true"></i>
|
||||
</button>
|
||||
</nb-card-header>
|
||||
<nb-card-body>
|
||||
<nb-tabset>
|
||||
<nb-tab tabTitle="{{ 'KEY_RESULT_PAGE.FORM.LABELS.WEIGHT' | translate }}">
|
||||
<!-- What a weight does, as a quiet note rather than two lines of link-coloured text. -->
|
||||
<p class="hint">{{ 'KEY_RESULT_PAGE.WEIGHT.MESSAGE' | translate }}</p>
|
||||
<form class="parameters-form" [formGroup]="weightForm">
|
||||
<div class="field span-3">
|
||||
<label for="key-result-weight" class="label">
|
||||
{{ 'KEY_RESULT_PAGE.FORM.LABELS.WEIGHT' | translate }}
|
||||
</label>
|
||||
<nb-select id="key-result-weight" fullWidth formControlName="weight">
|
||||
@for (weight of keyResultWeightEnum | keyvalue; track weight) {
|
||||
<nb-option [value]="weight.value">
|
||||
{{ 'KEY_RESULT_PAGE.WEIGHT.' + weight.key | translate }}
|
||||
</nb-option>
|
||||
}
|
||||
</nb-select>
|
||||
</div>
|
||||
<!-- What that weight comes to against the objective's other key results. -->
|
||||
<div class="field span-3 share">
|
||||
<label for="goal-progress-comparison" class="label">
|
||||
{{ 'KEY_RESULT_PAGE.WEIGHT.OBJECTIVE_PROGRESS' | translate : { weight: keyResultWeight } }}
|
||||
</label>
|
||||
<div class="share-row">
|
||||
<span class="share-value">{{ keyResultWeight }}%</span>
|
||||
<nb-progress-bar
|
||||
class="share-bar"
|
||||
id="goal-progress-comparison"
|
||||
[value]="keyResultWeight"
|
||||
status="primary"
|
||||
size="tiny"
|
||||
>
|
||||
</nb-progress-bar>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</nb-tab>
|
||||
<nb-tab tabTitle="{{ 'KEY_RESULT_PAGE.FORM.LABELS.TYPE' | translate }}">
|
||||
<form class="parameters-form" [formGroup]="typeForm">
|
||||
<ga-keyresult-type-select
|
||||
[parentFormGroup]="typeForm"
|
||||
[orgId]="data.orgId"
|
||||
[KPIs]="KPIs"
|
||||
[settings]="data.settings"
|
||||
[numberUnits]="numberUnitsEnum"
|
||||
></ga-keyresult-type-select>
|
||||
</form>
|
||||
</nb-tab>
|
||||
</nb-tabset>
|
||||
</nb-card-body>
|
||||
<nb-card-footer class="footer">
|
||||
<button nbButton ghost status="basic" size="small" (click)="closeDialog(null)">
|
||||
{{ 'BUTTONS.CANCEL' | translate }}
|
||||
</button>
|
||||
<button nbButton status="success" size="small" (click)="updateKeyResult()">
|
||||
{{ 'BUTTONS.SAVE' | translate }}
|
||||
</button>
|
||||
</nb-card-footer>
|
||||
</nb-card>
|
||||
|
||||
+295
@@ -0,0 +1,295 @@
|
||||
@use 'gauzy/_gauzy-dialogs' as *;
|
||||
|
||||
// The Edit Key Result Parameters dialog, on the same scale as the other goals
|
||||
// dialogs: a header row, 12px tabs over a panel that sizes to its content, 12px
|
||||
// labels over 2rem controls, and a footer whose buttons sit on the trailing
|
||||
// edge. It used to borrow the Edit Key Result stylesheet, which dressed none of
|
||||
// this. Every custom property carries a fallback — `--gauzy-*` is undefined in
|
||||
// material-light / material-dark, where a `var()` without one is dropped.
|
||||
$control-height: 2rem;
|
||||
$control-font-size: 12px;
|
||||
$control-padding-x: 0.75rem;
|
||||
$inset: 1rem;
|
||||
$divider: 1px solid var(--gauzy-border-default-color, rgba(126, 126, 143, 0.18));
|
||||
$muted: var(--gauzy-text-color-2, rgba(126, 126, 143, 1));
|
||||
$single-column-below: 575.98px;
|
||||
|
||||
// Was a hard 645 x 390px, which left a third of the sheet empty under two
|
||||
// fields and clipped the Type tab's own.
|
||||
.parameters-dialog {
|
||||
width: min(calc(100vw - 2rem), 40rem);
|
||||
max-height: calc(100vh - 4rem);
|
||||
}
|
||||
|
||||
:host {
|
||||
nb-card {
|
||||
background-color: var(--gauzy-card-1, var(--card-background-color));
|
||||
}
|
||||
|
||||
nb-card-header.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.75rem;
|
||||
padding: 0.875rem $inset;
|
||||
border-bottom: $divider;
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
font-size: $control-font-size;
|
||||
font-weight: 600;
|
||||
line-height: 1rem;
|
||||
color: var(--gauzy-text-color-1, inherit);
|
||||
}
|
||||
|
||||
// A real button, so the close is reachable from the keyboard and named for
|
||||
// a screen reader; the browser's button chrome is stripped.
|
||||
.cancel {
|
||||
width: auto;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: none;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid nb-theme(color-primary-default);
|
||||
outline-offset: 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nb-card-body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
// Tabs at 12px; the active one keeps the app's tinted tab, which joins the
|
||||
// panel under it.
|
||||
nb-tabset {
|
||||
::ng-deep .tabset {
|
||||
padding: 0 $inset;
|
||||
border-bottom: $divider;
|
||||
}
|
||||
|
||||
::ng-deep .tab-link,
|
||||
::ng-deep .tab-text {
|
||||
font-size: $control-font-size;
|
||||
font-weight: 600;
|
||||
line-height: 1rem;
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
::ng-deep .tab-link {
|
||||
padding: 0.625rem 0.875rem;
|
||||
}
|
||||
}
|
||||
|
||||
// A floor rather than a fixed height, so the Weight tab does not leave a
|
||||
// third of the dialog empty and the Type tab is never cut off. No `display`
|
||||
// here: Nebular hides an inactive tab with `display: none` on this element,
|
||||
// and overriding it showed both tabs' fields at once.
|
||||
nb-tab {
|
||||
min-height: 9rem;
|
||||
padding: $inset;
|
||||
background-color: var(--gauzy-card-2, rgba(126, 126, 143, 0.06));
|
||||
}
|
||||
|
||||
// Was `.info`, painted in the info accent: a two-line paragraph louder than
|
||||
// the field it explains.
|
||||
.hint {
|
||||
margin: 0 0 0.875rem;
|
||||
font-size: $control-font-size;
|
||||
line-height: 1.125rem;
|
||||
color: $muted;
|
||||
}
|
||||
|
||||
.parameters-form {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, minmax(0, 1fr));
|
||||
gap: 0.875rem 0.75rem;
|
||||
align-items: start;
|
||||
|
||||
@media (max-width: $single-column-below) {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.field {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.span-3 {
|
||||
grid-column: span 3;
|
||||
|
||||
@media (max-width: $single-column-below) {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
// The type picker renders its fields inside wrappers of its own; taking those
|
||||
// out of the box tree makes the fields items of this grid.
|
||||
::ng-deep ga-keyresult-type-select,
|
||||
::ng-deep ga-keyresult-type-select > div {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
::ng-deep .kr-field {
|
||||
min-width: 0;
|
||||
grid-column: span 3;
|
||||
|
||||
@media (max-width: $single-column-below) {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
// The value trio on a row of its own, under Type, as in the Edit Key Result
|
||||
// dialog; a task's project and task pair up the same way.
|
||||
::ng-deep .kr-field-initial {
|
||||
grid-column: 1 / span 2;
|
||||
}
|
||||
|
||||
::ng-deep .kr-field-target,
|
||||
::ng-deep .kr-field-unit {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
::ng-deep .kr-field-project {
|
||||
grid-column: 1 / span 3;
|
||||
}
|
||||
|
||||
@media (max-width: $single-column-below) {
|
||||
::ng-deep .kr-field-initial,
|
||||
::ng-deep .kr-field-target,
|
||||
::ng-deep .kr-field-unit,
|
||||
::ng-deep .kr-field-project {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
// The share this weight comes to, as a figure with a bar under its label
|
||||
// rather than a bar stretched across half the dialog.
|
||||
.share-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
min-height: $control-height;
|
||||
}
|
||||
|
||||
.share-value {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
line-height: 1rem;
|
||||
color: var(--gauzy-text-color-1, inherit);
|
||||
}
|
||||
|
||||
.share-bar {
|
||||
flex: 1 1 auto;
|
||||
width: 100%;
|
||||
|
||||
::ng-deep .progress-container {
|
||||
height: 6px !important;
|
||||
border-radius: 999px;
|
||||
background-color: var(--gauzy-border-default-color, rgba(126, 126, 143, 0.18));
|
||||
}
|
||||
|
||||
::ng-deep .progress-value {
|
||||
border-radius: 999px;
|
||||
|
||||
span {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep label.label {
|
||||
display: block;
|
||||
margin: 0 0 0.375rem;
|
||||
font-size: $control-font-size;
|
||||
font-weight: 600;
|
||||
line-height: 1rem;
|
||||
color: $muted;
|
||||
}
|
||||
|
||||
::ng-deep label.label.mt-3 {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
::ng-deep input[nbInput] {
|
||||
height: $control-height;
|
||||
// `!important` against the shared `dialog()` mixin, which pins every input
|
||||
// and select in a dialog to a 2.375rem `min-height` with `!important`.
|
||||
min-height: $control-height !important;
|
||||
padding: 0 $control-padding-x;
|
||||
font-size: $control-font-size;
|
||||
line-height: 1rem;
|
||||
}
|
||||
|
||||
// Both selectors on purpose: the `dialog()` mixin pins the `.status-basic`
|
||||
// one with `!important`, so only a selector of its shape can win, while the
|
||||
// bare one still reaches a select carrying another status.
|
||||
::ng-deep nb-select .select-button,
|
||||
::ng-deep nb-select.appearance-outline.status-basic .select-button {
|
||||
height: $control-height;
|
||||
min-height: $control-height !important;
|
||||
padding-block: 0;
|
||||
padding-inline-start: $control-padding-x !important;
|
||||
font-size: $control-font-size;
|
||||
line-height: 1rem;
|
||||
}
|
||||
|
||||
::ng-deep .ng-select .ng-select-container {
|
||||
min-height: $control-height;
|
||||
font-size: $control-font-size;
|
||||
}
|
||||
|
||||
// Where a KPI select goes once a KPI exists: a dashed slot the height of a
|
||||
// control, rather than a filled primary button in the middle of the form.
|
||||
::ng-deep .add-inline {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.375rem;
|
||||
width: 100%;
|
||||
height: $control-height;
|
||||
margin: 0;
|
||||
padding: 0 $control-padding-x;
|
||||
border: 1px dashed var(--gauzy-border-default-color, rgba(126, 126, 143, 0.4));
|
||||
border-radius: var(--gauzy-radius-sm, 0.375rem);
|
||||
background: transparent;
|
||||
font-size: $control-font-size;
|
||||
font-weight: 600;
|
||||
line-height: 1rem;
|
||||
color: nb-theme(color-primary-default);
|
||||
cursor: pointer;
|
||||
|
||||
i {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: nb-theme(color-primary-default);
|
||||
background-color: var(--gauzy-hover-tint, rgba(126, 126, 143, 0.12));
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid nb-theme(color-primary-default);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
// Buttons on the trailing edge, Cancel quiet beside the primary action.
|
||||
nb-card-footer.footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem $inset;
|
||||
border-top: $divider;
|
||||
|
||||
button {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -18,7 +18,7 @@ import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
|
||||
@Component({
|
||||
selector: 'ga-key-result-parameters',
|
||||
templateUrl: './key-result-parameters.component.html',
|
||||
styleUrls: ['../edit-keyresults/edit-keyresults.component.scss'],
|
||||
styleUrls: ['./key-result-parameters.component.scss'],
|
||||
standalone: false
|
||||
})
|
||||
export class KeyResultParametersComponent implements OnInit, OnDestroy {
|
||||
|
||||
+82
-111
@@ -1,126 +1,97 @@
|
||||
<div [formGroup]="parentFormGroup">
|
||||
<div [class.row]="enableHelperText">
|
||||
<div [class.col-md-7]="enableHelperText">
|
||||
<label for="key-result-type" class="label mt-3">
|
||||
{{ 'KEY_RESULT_PAGE.FORM.LABELS.KEY_RESULT_TYPE' | translate }}
|
||||
</label>
|
||||
<nb-select (selectedChange)="taskTypeValidators()" id="key-result-type" fullWidth formControlName="type">
|
||||
@for (type of keyResultTypeEnum | keyvalue; track type.key) {
|
||||
<nb-option [hidden]="isTypeHidden(type.value)" [value]="type.value">
|
||||
{{ 'KEY_RESULT_PAGE.TYPE.' + type.key | translate }}
|
||||
</nb-option>
|
||||
}
|
||||
</nb-select>
|
||||
</div>
|
||||
@if (enableHelperText) {
|
||||
<div class="col-md-5 position-relative mt-3"></div>
|
||||
}
|
||||
<!-- The type, and whatever that type needs, as sibling fields: each carries `kr-field` so the dialog around
|
||||
them can place them on its own grid. They used to sit one per bootstrap row, each with an empty
|
||||
five-column spacer beside it, which is what made this picker a stack of half-width rows. -->
|
||||
<div class="kr-type-fields" [formGroup]="parentFormGroup">
|
||||
<div class="kr-field kr-field-type">
|
||||
<label for="key-result-type" class="label">
|
||||
{{ 'KEY_RESULT_PAGE.FORM.LABELS.KEY_RESULT_TYPE' | translate }}
|
||||
</label>
|
||||
<nb-select (selectedChange)="taskTypeValidators()" id="key-result-type" fullWidth formControlName="type">
|
||||
@for (type of keyResultTypeEnum | keyvalue; track type.key) {
|
||||
<nb-option [hidden]="isTypeHidden(type.value)" [value]="type.value">
|
||||
{{ 'KEY_RESULT_PAGE.TYPE.' + type.key | translate }}
|
||||
</nb-option>
|
||||
}
|
||||
</nb-select>
|
||||
</div>
|
||||
|
||||
@if (parentFormGroup.value.type === keyResultTypeEnum.TASK) {
|
||||
<div [class.row]="enableHelperText">
|
||||
<div [class.col-md-7]="enableHelperText">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<label class="label mt-3" for="project-selector">{{
|
||||
'KEY_RESULT_PAGE.FORM.LABELS.SELECT_PROJECT' | translate
|
||||
}}</label>
|
||||
<ga-project-selector id="project-selector" formControlName="projectId"> </ga-project-selector>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="label mt-3" for="task-selector">{{
|
||||
'KEY_RESULT_PAGE.FORM.LABELS.SELECT_TASK' | translate
|
||||
}}</label>
|
||||
<ga-task-selector
|
||||
id="task-selector"
|
||||
formControlName="taskId"
|
||||
[projectId]="parentFormGroup.value.projectId"
|
||||
>
|
||||
</ga-task-selector>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if (enableHelperText) {
|
||||
<div class="col-md-5 position-relative mt-3"></div>
|
||||
}
|
||||
<div class="kr-field kr-field-project">
|
||||
<label class="label" for="project-selector">
|
||||
{{ 'KEY_RESULT_PAGE.FORM.LABELS.SELECT_PROJECT' | translate }}
|
||||
</label>
|
||||
<ga-project-selector id="project-selector" formControlName="projectId"></ga-project-selector>
|
||||
</div>
|
||||
<div class="kr-field kr-field-task">
|
||||
<label class="label" for="task-selector">
|
||||
{{ 'KEY_RESULT_PAGE.FORM.LABELS.SELECT_TASK' | translate }}
|
||||
</label>
|
||||
<ga-task-selector
|
||||
id="task-selector"
|
||||
formControlName="taskId"
|
||||
[projectId]="parentFormGroup.value.projectId"
|
||||
></ga-task-selector>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div [class.row]="enableHelperText">
|
||||
<div [class.col-md-7]="enableHelperText">
|
||||
@if (parentFormGroup.value.type === keyResultTypeEnum.KPI) {
|
||||
<div>
|
||||
<label for="kpi" class="label mt-3">
|
||||
{{ 'KEY_RESULT_PAGE.FORM.LABELS.SELECT_KPI' | translate }}
|
||||
</label>
|
||||
@if (KPIs.length > 0) {
|
||||
<nb-select id="kpi" fullWidth formControlName="kpiId">
|
||||
@for (kpi of KPIs; track kpi.id) {
|
||||
<nb-option [value]="kpi.id">
|
||||
{{ kpi.name }}
|
||||
</nb-option>
|
||||
}
|
||||
</nb-select>
|
||||
} @else {
|
||||
<p>
|
||||
<button nbButton status="primary" id="kpi" (click)="openEditKPI()">
|
||||
{{ 'BUTTONS.ADD_KPI' | translate }}
|
||||
</button>
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
@if (parentFormGroup.value.type === keyResultTypeEnum.KPI) {
|
||||
<div class="kr-field kr-field-kpi">
|
||||
<label for="kpi" class="label">
|
||||
{{ 'KEY_RESULT_PAGE.FORM.LABELS.SELECT_KPI' | translate }}
|
||||
</label>
|
||||
@if (KPIs?.length) {
|
||||
<nb-select id="kpi" fullWidth formControlName="kpiId">
|
||||
@for (kpi of KPIs; track kpi.id) {
|
||||
<nb-option [value]="kpi.id">
|
||||
{{ kpi.name }}
|
||||
</nb-option>
|
||||
}
|
||||
</div>
|
||||
@if (enableHelperText) {
|
||||
<div class="col-md-5 position-relative mt-3"></div>
|
||||
</nb-select>
|
||||
} @else {
|
||||
<!-- No KPI to pick yet: an empty slot where the select will be, rather than a filled primary button. -->
|
||||
<button type="button" id="kpi" class="add-inline" (click)="openEditKPI()">
|
||||
<i class="fas fa-plus" aria-hidden="true"></i>
|
||||
{{ 'BUTTONS.ADD_KPI' | translate }}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@if ( parentFormGroup.value.type === keyResultTypeEnum.NUMERICAL || parentFormGroup.value.type ===
|
||||
keyResultTypeEnum.CURRENCY ) {
|
||||
<div [class.row]="enableHelperText">
|
||||
<div [class.col-md-7]="enableHelperText">
|
||||
<div class="row mt-2">
|
||||
<div class="col-md-4">
|
||||
<label for="initial-value" class="label">
|
||||
{{ 'KEY_RESULT_PAGE.FORM.LABELS.INITIAL_VALUE' | translate }}
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
[min]="0"
|
||||
id="initial-value"
|
||||
nbInput
|
||||
fullWidth
|
||||
formControlName="initialValue"
|
||||
placeholder="{{ 'KEY_RESULT_PAGE.FORM.LABELS.INITIAL_VALUE' | translate }}"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label for="target-value" class="label">
|
||||
{{ 'KEY_RESULT_PAGE.FORM.LABELS.TARGET_VALUE' | translate }}
|
||||
</label>
|
||||
<input
|
||||
class="d-flex space-between"
|
||||
type="number"
|
||||
id="target-value"
|
||||
nbInput
|
||||
[min]="0"
|
||||
fullWidth
|
||||
formControlName="targetValue"
|
||||
placeholder="{{ 'KEY_RESULT_PAGE.FORM.LABELS.TARGET_VALUE' | translate }}"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<ga-goal-custom-unit-select
|
||||
[parentFormGroup]="parentFormGroup"
|
||||
[numberUnits]="numberUnits"
|
||||
></ga-goal-custom-unit-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if (enableHelperText) {
|
||||
<div class="col-md-5 position-relative mt-3"></div>
|
||||
}
|
||||
<div class="kr-field kr-field-initial">
|
||||
<label for="initial-value" class="label">
|
||||
{{ 'KEY_RESULT_PAGE.FORM.LABELS.INITIAL_VALUE' | translate }}
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
[min]="0"
|
||||
id="initial-value"
|
||||
nbInput
|
||||
fullWidth
|
||||
formControlName="initialValue"
|
||||
placeholder="{{ 'KEY_RESULT_PAGE.FORM.LABELS.INITIAL_VALUE' | translate }}"
|
||||
/>
|
||||
</div>
|
||||
<div class="kr-field kr-field-target">
|
||||
<label for="target-value" class="label">
|
||||
{{ 'KEY_RESULT_PAGE.FORM.LABELS.TARGET_VALUE' | translate }}
|
||||
</label>
|
||||
<input
|
||||
type="number"
|
||||
id="target-value"
|
||||
nbInput
|
||||
[min]="0"
|
||||
fullWidth
|
||||
formControlName="targetValue"
|
||||
placeholder="{{ 'KEY_RESULT_PAGE.FORM.LABELS.TARGET_VALUE' | translate }}"
|
||||
/>
|
||||
</div>
|
||||
<div class="kr-field kr-field-unit">
|
||||
<ga-goal-custom-unit-select
|
||||
[parentFormGroup]="parentFormGroup"
|
||||
[numberUnits]="numberUnits"
|
||||
></ga-goal-custom-unit-select>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
-1
@@ -18,7 +18,6 @@ export class KeyresultTypeSelectComponent {
|
||||
@Input() orgId: string;
|
||||
@Input() KPIs: Array<IKPI>;
|
||||
@Input() numberUnits: string[];
|
||||
@Input() enableHelperText = true;
|
||||
|
||||
keyResultTypeEnum = KeyResultTypeEnum;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user