@elasticias/ui 1.0.18 → 1.0.19
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.
|
@@ -1278,6 +1278,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
1278
1278
|
class EfDatepickerAdvancedComponent extends AbstractEfFormControl {
|
|
1279
1279
|
host = inject(ElementRef);
|
|
1280
1280
|
locale = inject(LOCALE_ID);
|
|
1281
|
+
translate = inject(TranslateService);
|
|
1281
1282
|
vcr = inject(ViewContainerRef);
|
|
1282
1283
|
destroyRef = inject(DestroyRef);
|
|
1283
1284
|
cdr = inject(ChangeDetectorRef);
|
|
@@ -2032,12 +2033,20 @@ class EfDatepickerAdvancedComponent extends AbstractEfFormControl {
|
|
|
2032
2033
|
return formatDate(today, 'd MMM', this.locale);
|
|
2033
2034
|
case 'this_week':
|
|
2034
2035
|
case 'this_month':
|
|
2035
|
-
|
|
2036
|
+
// Was `${n} j` — the French abbreviation for "jours",
|
|
2037
|
+
// hardcoded, so an English reader saw "6 j" and an Arabic
|
|
2038
|
+
// one saw it reversed to "j 6".
|
|
2039
|
+
return this.translate.instant('date_hint_days', {
|
|
2040
|
+
count: this.daysBetween(r.start, r.end),
|
|
2041
|
+
});
|
|
2036
2042
|
case 'last_30_days':
|
|
2037
2043
|
case 'last_90_days':
|
|
2038
2044
|
return `${formatDate(r.start, 'd MMM', this.locale)} — ${formatDate(r.end, 'd MMM', this.locale)}`;
|
|
2039
2045
|
case 'this_quarter':
|
|
2040
|
-
|
|
2046
|
+
// Was `T${n}` — French "Trimestre".
|
|
2047
|
+
return this.translate.instant('date_hint_quarter', {
|
|
2048
|
+
quarter: Math.floor(today.getMonth() / 3) + 1,
|
|
2049
|
+
});
|
|
2041
2050
|
case 'this_year':
|
|
2042
2051
|
return formatDate(today, 'yyyy', this.locale);
|
|
2043
2052
|
default:
|
|
@@ -2127,11 +2136,11 @@ class EfDatepickerAdvancedComponent extends AbstractEfFormControl {
|
|
|
2127
2136
|
trackByDay = (_, day) => day.date.getTime();
|
|
2128
2137
|
trackByDow = (_, label) => label;
|
|
2129
2138
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfDatepickerAdvancedComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2130
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfDatepickerAdvancedComponent, isStandalone: true, selector: "ef-datepicker-advanced", inputs: { triggerLabelKey: { classPropertyName: "triggerLabelKey", publicName: "triggerLabelKey", isSignal: true, isRequired: false, transformFunction: null }, triggerLabel: { classPropertyName: "triggerLabel", publicName: "triggerLabel", isSignal: true, isRequired: false, transformFunction: null }, presets: { classPropertyName: "presets", publicName: "presets", isSignal: true, isRequired: false, transformFunction: null }, activePreset: { classPropertyName: "activePreset", publicName: "activePreset", isSignal: true, isRequired: false, transformFunction: null }, start: { classPropertyName: "start", publicName: "start", isSignal: true, isRequired: false, transformFunction: null }, end: { classPropertyName: "end", publicName: "end", isSignal: true, isRequired: false, transformFunction: null }, allowCustomRange: { classPropertyName: "allowCustomRange", publicName: "allowCustomRange", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, selectionMode: { classPropertyName: "selectionMode", publicName: "selectionMode", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, showTime: { classPropertyName: "showTime", publicName: "showTime", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { rangeChange: "rangeChange", opened: "opened", closed: "closed" }, host: { listeners: { "document:click": "onDocumentClick($event)", "document:keydown.escape": "onEscape()" }, properties: { "class.dp-host--field": "mode() === 'field'" } }, viewQueries: [{ propertyName: "anchorEl", first: true, predicate: ["anchorEl"], descendants: true, static: true }, { propertyName: "panelTpl", first: true, predicate: ["panelTpl"], descendants: true, static: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"dp\" [class.dp--field]=\"mode() === 'field'\" #anchorEl>\n @if (mode() === 'field' && variant() === 'field') {\n <!-- Field mode \u2014 labelled field-box trigger (form control) -->\n @if (labelKey() || label()) {\n <ef-label\n [labelKey]=\"labelKey()\"\n [label]=\"label()\"\n [for]=\"effectiveId\"\n [required]=\"required()\"\n />\n }\n <div class=\"ef-field-control\">\n <button\n type=\"button\"\n class=\"dp__field-trigger\"\n [id]=\"effectiveId\"\n [class.open]=\"open()\"\n [class.is-invalid]=\"isInvalid\"\n [class.is-placeholder]=\"!hasFieldValue()\"\n [disabled]=\"isDisabled()\"\n [attr.aria-haspopup]=\"'dialog'\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-required]=\"required() ? 'true' : null\"\n [attr.aria-invalid]=\"isInvalid ? 'true' : null\"\n [attr.aria-describedby]=\"(showRequired || serverErrors) ? errorsId : null\"\n [attr.aria-label]=\"(labelKey() || label()) ? null : (ariaLabelKey() ? (ariaLabelKey() | translate) : (ariaLabel() || null))\"\n (click)=\"toggle($event)\"\n >\n <i class=\"ico pi pi-calendar\" aria-hidden=\"true\"></i>\n <span class=\"val\">\n {{ hasFieldValue() ? fieldDisplay() : (placeholderKey() ? (placeholderKey() | translate) : placeholder()) }}\n </span>\n <svg class=\"caret ef-chevron\" width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"6 9 12 15 18 9\" /></svg>\n </button>\n @if (canClear) {\n <!-- Overlaid sibling (not nested in the trigger <button>) so it\n clears instead of toggling the calendar. -->\n <ef-clear-button\n class=\"dp__field-clear\"\n [ariaLabel]=\"'action.clear' | translate\"\n (clear)=\"clearValue()\"\n />\n }\n @if (showRequired || serverErrors) {\n <div [id]=\"errorsId\" role=\"alert\" aria-live=\"polite\">\n @if (showRequired) {\n <small class=\"text-danger\">{{ 'validation.required' | translate }}</small>\n }\n @if (serverErrors; as errors) {\n @for (error of errors; track error) {\n <small class=\"text-danger block\">{{ error }}</small>\n }\n }\n </div>\n }\n </div>\n } @else {\n <!-- Pill trigger \u2014 filter mode, or field mode with variant=\"pill\" -->\n <button\n type=\"button\"\n class=\"dp__trigger\"\n [class.open]=\"open()\"\n [disabled]=\"isDisabled()\"\n [attr.aria-haspopup]=\"'dialog'\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-label]=\"mode() === 'field' && !(labelKey() || label()) ? (ariaLabelKey() ? (ariaLabelKey() | translate) : (ariaLabel() || null)) : null\"\n (click)=\"toggle($event)\"\n >\n <i class=\"ico pi pi-calendar\" aria-hidden=\"true\"></i>\n @if (mode() === 'field') {\n <span class=\"val\">\n {{ hasFieldValue() ? fieldDisplay() : (placeholderKey() ? (placeholderKey() | translate) : placeholder()) }}\n </span>\n } @else {\n <span class=\"lbl\">\n {{ triggerLabelKey() ? (triggerLabelKey() | translate) : triggerLabel() }}\n </span>\n <span class=\"val\">\n @if (resolvedRange().labelKey; as key) {\n {{ key | translate }}\n } @else {\n {{ resolvedRange().label }}\n }\n </span>\n }\n <svg class=\"caret ef-chevron\" width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"6 9 12 15 18 9\" /></svg>\n </button>\n }\n</div>\n\n<!--\n Popover panel \u2014 instantiated via ViewContainerRef + appended to\n <body> when `open()` flips on. Anchored absolutely to the trigger\n via positionPanel(); escapes ancestor overflow / stacking contexts.\n-->\n<ng-template #panelTpl>\n <div\n class=\"dp__panel open\"\n [class.wide]=\"view() === 'custom'\"\n role=\"dialog\"\n [attr.aria-label]=\"triggerLabelKey() | translate\"\n >\n <!-- \u2500\u2500 View 1 \u2014 preset list \u2500\u2500 -->\n <div class=\"dp__view\" [class.active]=\"view() === 'presets'\">\n @for (preset of effectivePresets(); track trackByPreset($index, preset)) {\n <button\n type=\"button\"\n class=\"dp__preset\"\n [class.active]=\"isActivePreset(preset.key)\"\n (click)=\"selectPreset(preset)\"\n >\n {{ preset.labelKey ? (preset.labelKey | translate) : preset.label }}\n <span class=\"hint\">{{ presetHint(preset) }}</span>\n </button>\n }\n\n @if (allowCustomRange()) {\n <div class=\"dp__divider\" role=\"separator\"></div>\n <button\n type=\"button\"\n class=\"dp__preset muted\"\n (click)=\"openCustomView()\"\n >\n <span style=\"display:inline-flex;align-items:center;gap:8px\">\n <i class=\"ico-lead pi pi-calendar\" aria-hidden=\"true\"></i>\n {{ 'date_preset_custom' | translate }}\n </span>\n <span class=\"hint\">\u21E7</span>\n </button>\n }\n </div>\n\n <!-- \u2500\u2500 View 2 \u2014 custom range with calendar \u2500\u2500 -->\n <div class=\"dp__view\" [class.active]=\"view() === 'custom'\">\n <div class=\"dp__custom-head\">\n @if (mode() !== 'field') {\n <button\n type=\"button\"\n class=\"dp__back\"\n [attr.aria-label]=\"'common_back' | translate\"\n (click)=\"backToPresets()\"\n >\n <i class=\"pi pi-chevron-left\" aria-hidden=\"true\"></i>\n </button>\n } @else {\n <span></span>\n }\n <div class=\"dp__custom-title\">\n <span class=\"pulse-dot\"></span>\n {{ (mode() === 'field' && selectionMode() === 'single' ? 'date_select_date' : 'date_custom_range') | translate }}\n </div>\n <span></span>\n </div>\n\n <div class=\"dp__cal\">\n <div class=\"dp__cal-head\">\n <button\n type=\"button\"\n class=\"dp__cal-nav\"\n [attr.aria-label]=\"'date_prev_month' | translate\"\n (click)=\"headerPrev()\"\n >\n <i class=\"pi pi-chevron-left\" aria-hidden=\"true\"></i>\n </button>\n <div class=\"dp__cal-title\">\n @switch (calMode()) {\n @case ('years') {\n <span class=\"dp__cal-pick is-static\">{{ yearWindowLabel() }}</span>\n }\n @case ('months') {\n <button type=\"button\" class=\"dp__cal-pick\" (click)=\"openYearPicker()\">\n {{ yearLabel() }}\n </button>\n }\n @default {\n <button type=\"button\" class=\"dp__cal-pick\" (click)=\"openMonthPicker()\">\n {{ monthLabel() }}\n </button>\n <button type=\"button\" class=\"dp__cal-pick\" (click)=\"openYearPicker()\">\n {{ yearLabel() }}\n </button>\n }\n }\n </div>\n <button\n type=\"button\"\n class=\"dp__cal-nav\"\n [attr.aria-label]=\"'date_next_month' | translate\"\n (click)=\"headerNext()\"\n >\n <i class=\"pi pi-chevron-right\" aria-hidden=\"true\"></i>\n </button>\n </div>\n\n @switch (calMode()) {\n @case ('months') {\n <div class=\"dp__cal-cells\">\n @for (cell of monthCells(); track cell.month) {\n <button\n type=\"button\"\n class=\"dp__cal-cell\"\n [class.active]=\"cell.active\"\n [class.today]=\"cell.today\"\n (click)=\"selectMonth(cell.month)\"\n >\n {{ cell.label }}\n </button>\n }\n </div>\n }\n @case ('years') {\n <div class=\"dp__cal-cells\">\n @for (cell of yearCells(); track cell.year) {\n <button\n type=\"button\"\n class=\"dp__cal-cell\"\n [class.active]=\"cell.active\"\n [class.today]=\"cell.today\"\n (click)=\"selectYear(cell.year)\"\n >\n {{ cell.year }}\n </button>\n }\n </div>\n }\n @default {\n <div class=\"dp__cal-dow-row\">\n @for (label of dowLabels(); track trackByDow($index, label)) {\n <div class=\"dp__cal-dow\">{{ label }}</div>\n }\n </div>\n\n <div class=\"dp__cal-grid\">\n @for (day of calendarGrid(); track trackByDay($index, day)) {\n <button\n type=\"button\"\n class=\"dp__cal-day\"\n [class.out]=\"day.out\"\n [class.today]=\"day.today\"\n [class.in-range]=\"day.inRange\"\n [class.range-start]=\"day.rangeStart\"\n [class.range-end]=\"day.rangeEnd\"\n [attr.aria-label]=\"day.date | date:'fullDate'\"\n (click)=\"selectDay(day.date)\"\n >\n <span class=\"day-num\">{{ day.day }}</span>\n </button>\n }\n </div>\n }\n }\n </div>\n\n @if (mode() === 'field' && selectionMode() === 'single') {\n <!-- Single-date field: date (+ time when enabled). Without time,\n picking a day commits & closes; with time, Apply commits. -->\n <div class=\"dp__inputs\" [class.dp__inputs--single]=\"!showTime()\">\n <div class=\"dp__field\">\n <label [attr.for]=\"effectiveId + '-single'\">{{ 'date_select_date' | translate }}</label>\n <input\n #singleInput\n [id]=\"effectiveId + '-single'\"\n type=\"text\"\n inputmode=\"numeric\"\n maxlength=\"10\"\n [value]=\"fromText()\"\n placeholder=\"JJ/MM/AAAA\"\n (keydown)=\"onDateKeydown($event, singleInput)\"\n (change)=\"commitSingleInput(singleInput)\"\n />\n </div>\n @if (showTime()) {\n <div class=\"dp__field\">\n <label [attr.for]=\"effectiveId + '-single-time'\">{{ 'date_time' | translate }}</label>\n <input\n #singleTime\n [id]=\"effectiveId + '-single-time'\"\n type=\"text\"\n inputmode=\"numeric\"\n maxlength=\"5\"\n [value]=\"fromTimeText()\"\n placeholder=\"HH:mm\"\n (keydown)=\"onTimeKeydown($event, singleTime)\"\n (change)=\"commitTimeInput('start', singleTime)\"\n />\n </div>\n }\n </div>\n } @else {\n <div class=\"dp__inputs\">\n <div class=\"dp__field\">\n <label [attr.for]=\"effectiveId + '-from'\">{{ 'date_from' | translate }}</label>\n <input\n #fromInput\n [id]=\"effectiveId + '-from'\"\n type=\"text\"\n inputmode=\"numeric\"\n maxlength=\"10\"\n [value]=\"fromText()\"\n placeholder=\"JJ/MM/AAAA\"\n (keydown)=\"onDateKeydown($event, fromInput)\"\n (change)=\"commitDateInput('start', fromInput)\"\n />\n @if (mode() === 'field' && showTime()) {\n <input\n #fromTime\n [attr.aria-label]=\"'date_time' | translate\"\n type=\"text\"\n inputmode=\"numeric\"\n maxlength=\"5\"\n [value]=\"fromTimeText()\"\n placeholder=\"HH:mm\"\n (keydown)=\"onTimeKeydown($event, fromTime)\"\n (change)=\"commitTimeInput('start', fromTime)\"\n />\n }\n </div>\n <div class=\"dp__field\">\n <label [attr.for]=\"effectiveId + '-to'\">{{ 'date_to' | translate }}</label>\n <input\n #toInput\n [id]=\"effectiveId + '-to'\"\n type=\"text\"\n inputmode=\"numeric\"\n maxlength=\"10\"\n [value]=\"toText()\"\n placeholder=\"JJ/MM/AAAA\"\n (keydown)=\"onDateKeydown($event, toInput)\"\n (change)=\"commitDateInput('end', toInput)\"\n />\n @if (mode() === 'field' && showTime()) {\n <input\n #toTime\n [attr.aria-label]=\"'date_time' | translate\"\n type=\"text\"\n inputmode=\"numeric\"\n maxlength=\"5\"\n [value]=\"toTimeText()\"\n placeholder=\"HH:mm\"\n (keydown)=\"onTimeKeydown($event, toTime)\"\n (change)=\"commitTimeInput('end', toTime)\"\n />\n }\n </div>\n </div>\n }\n\n <!-- Footer: hidden only for single field WITHOUT time (commit on pick). -->\n @if (!(mode() === 'field' && selectionMode() === 'single' && !showTime())) {\n <div class=\"dp__actions\">\n @if (mode() === 'field' && selectionMode() === 'single') {\n <span></span>\n } @else {\n <span class=\"duration\">\n <span class=\"num\">{{ draftDurationDays() }}</span>\n {{ 'date_days' | translate }}\n </span>\n }\n <div class=\"right\">\n @if (mode() !== 'field') {\n <button\n type=\"button\"\n class=\"btn btn-ghost btn-sm\"\n (click)=\"backToPresets()\"\n >\n {{ 'common_back' | translate }}\n </button>\n }\n <button\n type=\"button\"\n class=\"btn btn-primary btn-sm\"\n [disabled]=\"!draftStart()\"\n (click)=\"applyCustomRange()\"\n >\n {{ 'common_apply' | translate }}\n </button>\n </div>\n </div>\n }\n </div>\n </div>\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{display:inline-block}:host(.dp-host--field){display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfLabelComponent, selector: "ef-label", inputs: ["label", "labelKey", "for", "styleClass", "required"] }, { kind: "component", type: EfClearButtonComponent, selector: "ef-clear-button", inputs: ["ariaLabel", "disabled"], outputs: ["clear"] }, { kind: "pipe", type: i1$3.DatePipe, name: "date" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2139
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfDatepickerAdvancedComponent, isStandalone: true, selector: "ef-datepicker-advanced", inputs: { triggerLabelKey: { classPropertyName: "triggerLabelKey", publicName: "triggerLabelKey", isSignal: true, isRequired: false, transformFunction: null }, triggerLabel: { classPropertyName: "triggerLabel", publicName: "triggerLabel", isSignal: true, isRequired: false, transformFunction: null }, presets: { classPropertyName: "presets", publicName: "presets", isSignal: true, isRequired: false, transformFunction: null }, activePreset: { classPropertyName: "activePreset", publicName: "activePreset", isSignal: true, isRequired: false, transformFunction: null }, start: { classPropertyName: "start", publicName: "start", isSignal: true, isRequired: false, transformFunction: null }, end: { classPropertyName: "end", publicName: "end", isSignal: true, isRequired: false, transformFunction: null }, allowCustomRange: { classPropertyName: "allowCustomRange", publicName: "allowCustomRange", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, selectionMode: { classPropertyName: "selectionMode", publicName: "selectionMode", isSignal: true, isRequired: false, transformFunction: null }, variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, showTime: { classPropertyName: "showTime", publicName: "showTime", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { rangeChange: "rangeChange", opened: "opened", closed: "closed" }, host: { listeners: { "document:click": "onDocumentClick($event)", "document:keydown.escape": "onEscape()" }, properties: { "class.dp-host--field": "mode() === 'field'" } }, viewQueries: [{ propertyName: "anchorEl", first: true, predicate: ["anchorEl"], descendants: true, static: true }, { propertyName: "panelTpl", first: true, predicate: ["panelTpl"], descendants: true, static: true }], usesInheritance: true, ngImport: i0, template: "<div class=\"dp\" [class.dp--field]=\"mode() === 'field'\" #anchorEl>\n @if (mode() === 'field' && variant() === 'field') {\n <!-- Field mode \u2014 labelled field-box trigger (form control) -->\n @if (labelKey() || label()) {\n <ef-label\n [labelKey]=\"labelKey()\"\n [label]=\"label()\"\n [for]=\"effectiveId\"\n [required]=\"required()\"\n />\n }\n <div class=\"ef-field-control\">\n <button\n type=\"button\"\n class=\"dp__field-trigger\"\n [id]=\"effectiveId\"\n [class.open]=\"open()\"\n [class.is-invalid]=\"isInvalid\"\n [class.is-placeholder]=\"!hasFieldValue()\"\n [disabled]=\"isDisabled()\"\n [attr.aria-haspopup]=\"'dialog'\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-required]=\"required() ? 'true' : null\"\n [attr.aria-invalid]=\"isInvalid ? 'true' : null\"\n [attr.aria-describedby]=\"(showRequired || serverErrors) ? errorsId : null\"\n [attr.aria-label]=\"(labelKey() || label()) ? null : (ariaLabelKey() ? (ariaLabelKey() | translate) : (ariaLabel() || null))\"\n (click)=\"toggle($event)\"\n >\n <i class=\"ico pi pi-calendar\" aria-hidden=\"true\"></i>\n <span class=\"val\">\n {{ hasFieldValue() ? fieldDisplay() : (placeholderKey() ? (placeholderKey() | translate) : placeholder()) }}\n </span>\n <svg class=\"caret ef-chevron\" width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"6 9 12 15 18 9\" /></svg>\n </button>\n @if (canClear) {\n <!-- Overlaid sibling (not nested in the trigger <button>) so it\n clears instead of toggling the calendar. -->\n <ef-clear-button\n class=\"dp__field-clear\"\n [ariaLabel]=\"'action.clear' | translate\"\n (clear)=\"clearValue()\"\n />\n }\n @if (showRequired || serverErrors) {\n <div [id]=\"errorsId\" role=\"alert\" aria-live=\"polite\">\n @if (showRequired) {\n <small class=\"text-danger\">{{ 'validation.required' | translate }}</small>\n }\n @if (serverErrors; as errors) {\n @for (error of errors; track error) {\n <small class=\"text-danger block\">{{ error }}</small>\n }\n }\n </div>\n }\n </div>\n } @else {\n <!-- Pill trigger \u2014 filter mode, or field mode with variant=\"pill\" -->\n <button\n type=\"button\"\n class=\"dp__trigger\"\n [class.open]=\"open()\"\n [disabled]=\"isDisabled()\"\n [attr.aria-haspopup]=\"'dialog'\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-label]=\"mode() === 'field' && !(labelKey() || label()) ? (ariaLabelKey() ? (ariaLabelKey() | translate) : (ariaLabel() || null)) : null\"\n (click)=\"toggle($event)\"\n >\n <i class=\"ico pi pi-calendar\" aria-hidden=\"true\"></i>\n @if (mode() === 'field') {\n <span class=\"val\">\n {{ hasFieldValue() ? fieldDisplay() : (placeholderKey() ? (placeholderKey() | translate) : placeholder()) }}\n </span>\n } @else {\n <span class=\"lbl\">\n {{ triggerLabelKey() ? (triggerLabelKey() | translate) : triggerLabel() }}\n </span>\n <span class=\"val\">\n @if (resolvedRange().labelKey; as key) {\n {{ key | translate }}\n } @else {\n {{ resolvedRange().label }}\n }\n </span>\n }\n <svg class=\"caret ef-chevron\" width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"6 9 12 15 18 9\" /></svg>\n </button>\n }\n</div>\n\n<!--\n Popover panel \u2014 instantiated via ViewContainerRef + appended to\n <body> when `open()` flips on. Anchored absolutely to the trigger\n via positionPanel(); escapes ancestor overflow / stacking contexts.\n-->\n<ng-template #panelTpl>\n <div\n class=\"dp__panel open\"\n [class.wide]=\"view() === 'custom'\"\n role=\"dialog\"\n [attr.aria-label]=\"triggerLabelKey() | translate\"\n >\n <!-- \u2500\u2500 View 1 \u2014 preset list \u2500\u2500 -->\n <div class=\"dp__view\" [class.active]=\"view() === 'presets'\">\n @for (preset of effectivePresets(); track trackByPreset($index, preset)) {\n <button\n type=\"button\"\n class=\"dp__preset\"\n [class.active]=\"isActivePreset(preset.key)\"\n (click)=\"selectPreset(preset)\"\n >\n {{ preset.labelKey ? (preset.labelKey | translate) : preset.label }}\n <span class=\"hint\">{{ presetHint(preset) }}</span>\n </button>\n }\n\n @if (allowCustomRange()) {\n <div class=\"dp__divider\" role=\"separator\"></div>\n <button\n type=\"button\"\n class=\"dp__preset muted\"\n (click)=\"openCustomView()\"\n >\n <span style=\"display:inline-flex;align-items:center;gap:8px\">\n <i class=\"ico-lead pi pi-calendar\" aria-hidden=\"true\"></i>\n {{ 'date_preset_custom' | translate }}\n </span>\n <span class=\"hint\">\u21E7</span>\n </button>\n }\n </div>\n\n <!-- \u2500\u2500 View 2 \u2014 custom range with calendar \u2500\u2500 -->\n <div class=\"dp__view\" [class.active]=\"view() === 'custom'\">\n <div class=\"dp__custom-head\">\n @if (mode() !== 'field') {\n <button\n type=\"button\"\n class=\"dp__back\"\n [attr.aria-label]=\"'common_back' | translate\"\n (click)=\"backToPresets()\"\n >\n <i class=\"pi pi-chevron-left\" aria-hidden=\"true\"></i>\n </button>\n } @else {\n <span></span>\n }\n <div class=\"dp__custom-title\">\n <span class=\"pulse-dot\"></span>\n {{ (mode() === 'field' && selectionMode() === 'single' ? 'date_select_date' : 'date_custom_range') | translate }}\n </div>\n <span></span>\n </div>\n\n <div class=\"dp__cal\">\n <div class=\"dp__cal-head\">\n <button\n type=\"button\"\n class=\"dp__cal-nav\"\n [attr.aria-label]=\"'date_prev_month' | translate\"\n (click)=\"headerPrev()\"\n >\n <i class=\"pi pi-chevron-left\" aria-hidden=\"true\"></i>\n </button>\n <div class=\"dp__cal-title\">\n @switch (calMode()) {\n @case ('years') {\n <span class=\"dp__cal-pick is-static\">{{ yearWindowLabel() }}</span>\n }\n @case ('months') {\n <button type=\"button\" class=\"dp__cal-pick\" (click)=\"openYearPicker()\">\n {{ yearLabel() }}\n </button>\n }\n @default {\n <button type=\"button\" class=\"dp__cal-pick\" (click)=\"openMonthPicker()\">\n {{ monthLabel() }}\n </button>\n <button type=\"button\" class=\"dp__cal-pick\" (click)=\"openYearPicker()\">\n {{ yearLabel() }}\n </button>\n }\n }\n </div>\n <button\n type=\"button\"\n class=\"dp__cal-nav\"\n [attr.aria-label]=\"'date_next_month' | translate\"\n (click)=\"headerNext()\"\n >\n <i class=\"pi pi-chevron-right\" aria-hidden=\"true\"></i>\n </button>\n </div>\n\n @switch (calMode()) {\n @case ('months') {\n <div class=\"dp__cal-cells\">\n @for (cell of monthCells(); track cell.month) {\n <button\n type=\"button\"\n class=\"dp__cal-cell\"\n [class.active]=\"cell.active\"\n [class.today]=\"cell.today\"\n (click)=\"selectMonth(cell.month)\"\n >\n {{ cell.label }}\n </button>\n }\n </div>\n }\n @case ('years') {\n <div class=\"dp__cal-cells\">\n @for (cell of yearCells(); track cell.year) {\n <button\n type=\"button\"\n class=\"dp__cal-cell\"\n [class.active]=\"cell.active\"\n [class.today]=\"cell.today\"\n (click)=\"selectYear(cell.year)\"\n >\n {{ cell.year }}\n </button>\n }\n </div>\n }\n @default {\n <div class=\"dp__cal-dow-row\">\n @for (label of dowLabels(); track trackByDow($index, label)) {\n <div class=\"dp__cal-dow\">{{ label }}</div>\n }\n </div>\n\n <div class=\"dp__cal-grid\">\n @for (day of calendarGrid(); track trackByDay($index, day)) {\n <button\n type=\"button\"\n class=\"dp__cal-day\"\n [class.out]=\"day.out\"\n [class.today]=\"day.today\"\n [class.in-range]=\"day.inRange\"\n [class.range-start]=\"day.rangeStart\"\n [class.range-end]=\"day.rangeEnd\"\n [attr.aria-label]=\"day.date | date:'fullDate'\"\n (click)=\"selectDay(day.date)\"\n >\n <span class=\"day-num\">{{ day.day }}</span>\n </button>\n }\n </div>\n }\n }\n </div>\n\n @if (mode() === 'field' && selectionMode() === 'single') {\n <!-- Single-date field: date (+ time when enabled). Without time,\n picking a day commits & closes; with time, Apply commits. -->\n <div class=\"dp__inputs\" [class.dp__inputs--single]=\"!showTime()\">\n <div class=\"dp__field\">\n <label [attr.for]=\"effectiveId + '-single'\">{{ 'date_select_date' | translate }}</label>\n <input\n #singleInput\n [id]=\"effectiveId + '-single'\"\n type=\"text\"\n inputmode=\"numeric\"\n maxlength=\"10\"\n [value]=\"fromText()\"\n placeholder=\"JJ/MM/AAAA\"\n (keydown)=\"onDateKeydown($event, singleInput)\"\n (change)=\"commitSingleInput(singleInput)\"\n />\n </div>\n @if (showTime()) {\n <div class=\"dp__field\">\n <label [attr.for]=\"effectiveId + '-single-time'\">{{ 'date_time' | translate }}</label>\n <input\n #singleTime\n [id]=\"effectiveId + '-single-time'\"\n type=\"text\"\n inputmode=\"numeric\"\n maxlength=\"5\"\n [value]=\"fromTimeText()\"\n placeholder=\"HH:mm\"\n (keydown)=\"onTimeKeydown($event, singleTime)\"\n (change)=\"commitTimeInput('start', singleTime)\"\n />\n </div>\n }\n </div>\n } @else {\n <div class=\"dp__inputs\">\n <div class=\"dp__field\">\n <label [attr.for]=\"effectiveId + '-from'\">{{ 'date_from' | translate }}</label>\n <input\n #fromInput\n [id]=\"effectiveId + '-from'\"\n type=\"text\"\n inputmode=\"numeric\"\n maxlength=\"10\"\n [value]=\"fromText()\"\n placeholder=\"JJ/MM/AAAA\"\n (keydown)=\"onDateKeydown($event, fromInput)\"\n (change)=\"commitDateInput('start', fromInput)\"\n />\n @if (mode() === 'field' && showTime()) {\n <input\n #fromTime\n [attr.aria-label]=\"'date_time' | translate\"\n type=\"text\"\n inputmode=\"numeric\"\n maxlength=\"5\"\n [value]=\"fromTimeText()\"\n placeholder=\"HH:mm\"\n (keydown)=\"onTimeKeydown($event, fromTime)\"\n (change)=\"commitTimeInput('start', fromTime)\"\n />\n }\n </div>\n <div class=\"dp__field\">\n <label [attr.for]=\"effectiveId + '-to'\">{{ 'date_to' | translate }}</label>\n <input\n #toInput\n [id]=\"effectiveId + '-to'\"\n type=\"text\"\n inputmode=\"numeric\"\n maxlength=\"10\"\n [value]=\"toText()\"\n placeholder=\"JJ/MM/AAAA\"\n (keydown)=\"onDateKeydown($event, toInput)\"\n (change)=\"commitDateInput('end', toInput)\"\n />\n @if (mode() === 'field' && showTime()) {\n <input\n #toTime\n [attr.aria-label]=\"'date_time' | translate\"\n type=\"text\"\n inputmode=\"numeric\"\n maxlength=\"5\"\n [value]=\"toTimeText()\"\n placeholder=\"HH:mm\"\n (keydown)=\"onTimeKeydown($event, toTime)\"\n (change)=\"commitTimeInput('end', toTime)\"\n />\n }\n </div>\n </div>\n }\n\n <!-- Footer: hidden only for single field WITHOUT time (commit on pick). -->\n @if (!(mode() === 'field' && selectionMode() === 'single' && !showTime())) {\n <div class=\"dp__actions\">\n @if (mode() === 'field' && selectionMode() === 'single') {\n <span></span>\n } @else {\n <span class=\"duration\">\n <span class=\"num\">{{ draftDurationDays() }}</span>\n {{ 'date_days' | translate }}\n </span>\n }\n <div class=\"right\">\n @if (mode() !== 'field') {\n <button\n type=\"button\"\n class=\"btn btn-ghost btn-sm\"\n (click)=\"backToPresets()\"\n >\n {{ 'common_back' | translate }}\n </button>\n }\n <button\n type=\"button\"\n class=\"btn btn-primary btn-sm\"\n [disabled]=\"!draftStart()\"\n (click)=\"applyCustomRange()\"\n >\n {{ 'common_apply' | translate }}\n </button>\n </div>\n </div>\n }\n </div>\n </div>\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{display:inline-block}:host(.dp-host--field){display:block}.hint{unicode-bidi:plaintext}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfLabelComponent, selector: "ef-label", inputs: ["label", "labelKey", "for", "styleClass", "required"] }, { kind: "component", type: EfClearButtonComponent, selector: "ef-clear-button", inputs: ["ariaLabel", "disabled"], outputs: ["clear"] }, { kind: "pipe", type: i1$3.DatePipe, name: "date" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2131
2140
|
}
|
|
2132
2141
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfDatepickerAdvancedComponent, decorators: [{
|
|
2133
2142
|
type: Component,
|
|
2134
|
-
args: [{ selector: 'ef-datepicker-advanced', standalone: true, imports: [CommonModule, TranslateModule, EfLabelComponent, EfClearButtonComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: { '[class.dp-host--field]': "mode() === 'field'" }, template: "<div class=\"dp\" [class.dp--field]=\"mode() === 'field'\" #anchorEl>\n @if (mode() === 'field' && variant() === 'field') {\n <!-- Field mode \u2014 labelled field-box trigger (form control) -->\n @if (labelKey() || label()) {\n <ef-label\n [labelKey]=\"labelKey()\"\n [label]=\"label()\"\n [for]=\"effectiveId\"\n [required]=\"required()\"\n />\n }\n <div class=\"ef-field-control\">\n <button\n type=\"button\"\n class=\"dp__field-trigger\"\n [id]=\"effectiveId\"\n [class.open]=\"open()\"\n [class.is-invalid]=\"isInvalid\"\n [class.is-placeholder]=\"!hasFieldValue()\"\n [disabled]=\"isDisabled()\"\n [attr.aria-haspopup]=\"'dialog'\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-required]=\"required() ? 'true' : null\"\n [attr.aria-invalid]=\"isInvalid ? 'true' : null\"\n [attr.aria-describedby]=\"(showRequired || serverErrors) ? errorsId : null\"\n [attr.aria-label]=\"(labelKey() || label()) ? null : (ariaLabelKey() ? (ariaLabelKey() | translate) : (ariaLabel() || null))\"\n (click)=\"toggle($event)\"\n >\n <i class=\"ico pi pi-calendar\" aria-hidden=\"true\"></i>\n <span class=\"val\">\n {{ hasFieldValue() ? fieldDisplay() : (placeholderKey() ? (placeholderKey() | translate) : placeholder()) }}\n </span>\n <svg class=\"caret ef-chevron\" width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"6 9 12 15 18 9\" /></svg>\n </button>\n @if (canClear) {\n <!-- Overlaid sibling (not nested in the trigger <button>) so it\n clears instead of toggling the calendar. -->\n <ef-clear-button\n class=\"dp__field-clear\"\n [ariaLabel]=\"'action.clear' | translate\"\n (clear)=\"clearValue()\"\n />\n }\n @if (showRequired || serverErrors) {\n <div [id]=\"errorsId\" role=\"alert\" aria-live=\"polite\">\n @if (showRequired) {\n <small class=\"text-danger\">{{ 'validation.required' | translate }}</small>\n }\n @if (serverErrors; as errors) {\n @for (error of errors; track error) {\n <small class=\"text-danger block\">{{ error }}</small>\n }\n }\n </div>\n }\n </div>\n } @else {\n <!-- Pill trigger \u2014 filter mode, or field mode with variant=\"pill\" -->\n <button\n type=\"button\"\n class=\"dp__trigger\"\n [class.open]=\"open()\"\n [disabled]=\"isDisabled()\"\n [attr.aria-haspopup]=\"'dialog'\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-label]=\"mode() === 'field' && !(labelKey() || label()) ? (ariaLabelKey() ? (ariaLabelKey() | translate) : (ariaLabel() || null)) : null\"\n (click)=\"toggle($event)\"\n >\n <i class=\"ico pi pi-calendar\" aria-hidden=\"true\"></i>\n @if (mode() === 'field') {\n <span class=\"val\">\n {{ hasFieldValue() ? fieldDisplay() : (placeholderKey() ? (placeholderKey() | translate) : placeholder()) }}\n </span>\n } @else {\n <span class=\"lbl\">\n {{ triggerLabelKey() ? (triggerLabelKey() | translate) : triggerLabel() }}\n </span>\n <span class=\"val\">\n @if (resolvedRange().labelKey; as key) {\n {{ key | translate }}\n } @else {\n {{ resolvedRange().label }}\n }\n </span>\n }\n <svg class=\"caret ef-chevron\" width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"6 9 12 15 18 9\" /></svg>\n </button>\n }\n</div>\n\n<!--\n Popover panel \u2014 instantiated via ViewContainerRef + appended to\n <body> when `open()` flips on. Anchored absolutely to the trigger\n via positionPanel(); escapes ancestor overflow / stacking contexts.\n-->\n<ng-template #panelTpl>\n <div\n class=\"dp__panel open\"\n [class.wide]=\"view() === 'custom'\"\n role=\"dialog\"\n [attr.aria-label]=\"triggerLabelKey() | translate\"\n >\n <!-- \u2500\u2500 View 1 \u2014 preset list \u2500\u2500 -->\n <div class=\"dp__view\" [class.active]=\"view() === 'presets'\">\n @for (preset of effectivePresets(); track trackByPreset($index, preset)) {\n <button\n type=\"button\"\n class=\"dp__preset\"\n [class.active]=\"isActivePreset(preset.key)\"\n (click)=\"selectPreset(preset)\"\n >\n {{ preset.labelKey ? (preset.labelKey | translate) : preset.label }}\n <span class=\"hint\">{{ presetHint(preset) }}</span>\n </button>\n }\n\n @if (allowCustomRange()) {\n <div class=\"dp__divider\" role=\"separator\"></div>\n <button\n type=\"button\"\n class=\"dp__preset muted\"\n (click)=\"openCustomView()\"\n >\n <span style=\"display:inline-flex;align-items:center;gap:8px\">\n <i class=\"ico-lead pi pi-calendar\" aria-hidden=\"true\"></i>\n {{ 'date_preset_custom' | translate }}\n </span>\n <span class=\"hint\">\u21E7</span>\n </button>\n }\n </div>\n\n <!-- \u2500\u2500 View 2 \u2014 custom range with calendar \u2500\u2500 -->\n <div class=\"dp__view\" [class.active]=\"view() === 'custom'\">\n <div class=\"dp__custom-head\">\n @if (mode() !== 'field') {\n <button\n type=\"button\"\n class=\"dp__back\"\n [attr.aria-label]=\"'common_back' | translate\"\n (click)=\"backToPresets()\"\n >\n <i class=\"pi pi-chevron-left\" aria-hidden=\"true\"></i>\n </button>\n } @else {\n <span></span>\n }\n <div class=\"dp__custom-title\">\n <span class=\"pulse-dot\"></span>\n {{ (mode() === 'field' && selectionMode() === 'single' ? 'date_select_date' : 'date_custom_range') | translate }}\n </div>\n <span></span>\n </div>\n\n <div class=\"dp__cal\">\n <div class=\"dp__cal-head\">\n <button\n type=\"button\"\n class=\"dp__cal-nav\"\n [attr.aria-label]=\"'date_prev_month' | translate\"\n (click)=\"headerPrev()\"\n >\n <i class=\"pi pi-chevron-left\" aria-hidden=\"true\"></i>\n </button>\n <div class=\"dp__cal-title\">\n @switch (calMode()) {\n @case ('years') {\n <span class=\"dp__cal-pick is-static\">{{ yearWindowLabel() }}</span>\n }\n @case ('months') {\n <button type=\"button\" class=\"dp__cal-pick\" (click)=\"openYearPicker()\">\n {{ yearLabel() }}\n </button>\n }\n @default {\n <button type=\"button\" class=\"dp__cal-pick\" (click)=\"openMonthPicker()\">\n {{ monthLabel() }}\n </button>\n <button type=\"button\" class=\"dp__cal-pick\" (click)=\"openYearPicker()\">\n {{ yearLabel() }}\n </button>\n }\n }\n </div>\n <button\n type=\"button\"\n class=\"dp__cal-nav\"\n [attr.aria-label]=\"'date_next_month' | translate\"\n (click)=\"headerNext()\"\n >\n <i class=\"pi pi-chevron-right\" aria-hidden=\"true\"></i>\n </button>\n </div>\n\n @switch (calMode()) {\n @case ('months') {\n <div class=\"dp__cal-cells\">\n @for (cell of monthCells(); track cell.month) {\n <button\n type=\"button\"\n class=\"dp__cal-cell\"\n [class.active]=\"cell.active\"\n [class.today]=\"cell.today\"\n (click)=\"selectMonth(cell.month)\"\n >\n {{ cell.label }}\n </button>\n }\n </div>\n }\n @case ('years') {\n <div class=\"dp__cal-cells\">\n @for (cell of yearCells(); track cell.year) {\n <button\n type=\"button\"\n class=\"dp__cal-cell\"\n [class.active]=\"cell.active\"\n [class.today]=\"cell.today\"\n (click)=\"selectYear(cell.year)\"\n >\n {{ cell.year }}\n </button>\n }\n </div>\n }\n @default {\n <div class=\"dp__cal-dow-row\">\n @for (label of dowLabels(); track trackByDow($index, label)) {\n <div class=\"dp__cal-dow\">{{ label }}</div>\n }\n </div>\n\n <div class=\"dp__cal-grid\">\n @for (day of calendarGrid(); track trackByDay($index, day)) {\n <button\n type=\"button\"\n class=\"dp__cal-day\"\n [class.out]=\"day.out\"\n [class.today]=\"day.today\"\n [class.in-range]=\"day.inRange\"\n [class.range-start]=\"day.rangeStart\"\n [class.range-end]=\"day.rangeEnd\"\n [attr.aria-label]=\"day.date | date:'fullDate'\"\n (click)=\"selectDay(day.date)\"\n >\n <span class=\"day-num\">{{ day.day }}</span>\n </button>\n }\n </div>\n }\n }\n </div>\n\n @if (mode() === 'field' && selectionMode() === 'single') {\n <!-- Single-date field: date (+ time when enabled). Without time,\n picking a day commits & closes; with time, Apply commits. -->\n <div class=\"dp__inputs\" [class.dp__inputs--single]=\"!showTime()\">\n <div class=\"dp__field\">\n <label [attr.for]=\"effectiveId + '-single'\">{{ 'date_select_date' | translate }}</label>\n <input\n #singleInput\n [id]=\"effectiveId + '-single'\"\n type=\"text\"\n inputmode=\"numeric\"\n maxlength=\"10\"\n [value]=\"fromText()\"\n placeholder=\"JJ/MM/AAAA\"\n (keydown)=\"onDateKeydown($event, singleInput)\"\n (change)=\"commitSingleInput(singleInput)\"\n />\n </div>\n @if (showTime()) {\n <div class=\"dp__field\">\n <label [attr.for]=\"effectiveId + '-single-time'\">{{ 'date_time' | translate }}</label>\n <input\n #singleTime\n [id]=\"effectiveId + '-single-time'\"\n type=\"text\"\n inputmode=\"numeric\"\n maxlength=\"5\"\n [value]=\"fromTimeText()\"\n placeholder=\"HH:mm\"\n (keydown)=\"onTimeKeydown($event, singleTime)\"\n (change)=\"commitTimeInput('start', singleTime)\"\n />\n </div>\n }\n </div>\n } @else {\n <div class=\"dp__inputs\">\n <div class=\"dp__field\">\n <label [attr.for]=\"effectiveId + '-from'\">{{ 'date_from' | translate }}</label>\n <input\n #fromInput\n [id]=\"effectiveId + '-from'\"\n type=\"text\"\n inputmode=\"numeric\"\n maxlength=\"10\"\n [value]=\"fromText()\"\n placeholder=\"JJ/MM/AAAA\"\n (keydown)=\"onDateKeydown($event, fromInput)\"\n (change)=\"commitDateInput('start', fromInput)\"\n />\n @if (mode() === 'field' && showTime()) {\n <input\n #fromTime\n [attr.aria-label]=\"'date_time' | translate\"\n type=\"text\"\n inputmode=\"numeric\"\n maxlength=\"5\"\n [value]=\"fromTimeText()\"\n placeholder=\"HH:mm\"\n (keydown)=\"onTimeKeydown($event, fromTime)\"\n (change)=\"commitTimeInput('start', fromTime)\"\n />\n }\n </div>\n <div class=\"dp__field\">\n <label [attr.for]=\"effectiveId + '-to'\">{{ 'date_to' | translate }}</label>\n <input\n #toInput\n [id]=\"effectiveId + '-to'\"\n type=\"text\"\n inputmode=\"numeric\"\n maxlength=\"10\"\n [value]=\"toText()\"\n placeholder=\"JJ/MM/AAAA\"\n (keydown)=\"onDateKeydown($event, toInput)\"\n (change)=\"commitDateInput('end', toInput)\"\n />\n @if (mode() === 'field' && showTime()) {\n <input\n #toTime\n [attr.aria-label]=\"'date_time' | translate\"\n type=\"text\"\n inputmode=\"numeric\"\n maxlength=\"5\"\n [value]=\"toTimeText()\"\n placeholder=\"HH:mm\"\n (keydown)=\"onTimeKeydown($event, toTime)\"\n (change)=\"commitTimeInput('end', toTime)\"\n />\n }\n </div>\n </div>\n }\n\n <!-- Footer: hidden only for single field WITHOUT time (commit on pick). -->\n @if (!(mode() === 'field' && selectionMode() === 'single' && !showTime())) {\n <div class=\"dp__actions\">\n @if (mode() === 'field' && selectionMode() === 'single') {\n <span></span>\n } @else {\n <span class=\"duration\">\n <span class=\"num\">{{ draftDurationDays() }}</span>\n {{ 'date_days' | translate }}\n </span>\n }\n <div class=\"right\">\n @if (mode() !== 'field') {\n <button\n type=\"button\"\n class=\"btn btn-ghost btn-sm\"\n (click)=\"backToPresets()\"\n >\n {{ 'common_back' | translate }}\n </button>\n }\n <button\n type=\"button\"\n class=\"btn btn-primary btn-sm\"\n [disabled]=\"!draftStart()\"\n (click)=\"applyCustomRange()\"\n >\n {{ 'common_apply' | translate }}\n </button>\n </div>\n </div>\n }\n </div>\n </div>\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{display:inline-block}:host(.dp-host--field){display:block}\n"] }]
|
|
2143
|
+
args: [{ selector: 'ef-datepicker-advanced', standalone: true, imports: [CommonModule, TranslateModule, EfLabelComponent, EfClearButtonComponent], changeDetection: ChangeDetectionStrategy.OnPush, host: { '[class.dp-host--field]': "mode() === 'field'" }, template: "<div class=\"dp\" [class.dp--field]=\"mode() === 'field'\" #anchorEl>\n @if (mode() === 'field' && variant() === 'field') {\n <!-- Field mode \u2014 labelled field-box trigger (form control) -->\n @if (labelKey() || label()) {\n <ef-label\n [labelKey]=\"labelKey()\"\n [label]=\"label()\"\n [for]=\"effectiveId\"\n [required]=\"required()\"\n />\n }\n <div class=\"ef-field-control\">\n <button\n type=\"button\"\n class=\"dp__field-trigger\"\n [id]=\"effectiveId\"\n [class.open]=\"open()\"\n [class.is-invalid]=\"isInvalid\"\n [class.is-placeholder]=\"!hasFieldValue()\"\n [disabled]=\"isDisabled()\"\n [attr.aria-haspopup]=\"'dialog'\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-required]=\"required() ? 'true' : null\"\n [attr.aria-invalid]=\"isInvalid ? 'true' : null\"\n [attr.aria-describedby]=\"(showRequired || serverErrors) ? errorsId : null\"\n [attr.aria-label]=\"(labelKey() || label()) ? null : (ariaLabelKey() ? (ariaLabelKey() | translate) : (ariaLabel() || null))\"\n (click)=\"toggle($event)\"\n >\n <i class=\"ico pi pi-calendar\" aria-hidden=\"true\"></i>\n <span class=\"val\">\n {{ hasFieldValue() ? fieldDisplay() : (placeholderKey() ? (placeholderKey() | translate) : placeholder()) }}\n </span>\n <svg class=\"caret ef-chevron\" width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"6 9 12 15 18 9\" /></svg>\n </button>\n @if (canClear) {\n <!-- Overlaid sibling (not nested in the trigger <button>) so it\n clears instead of toggling the calendar. -->\n <ef-clear-button\n class=\"dp__field-clear\"\n [ariaLabel]=\"'action.clear' | translate\"\n (clear)=\"clearValue()\"\n />\n }\n @if (showRequired || serverErrors) {\n <div [id]=\"errorsId\" role=\"alert\" aria-live=\"polite\">\n @if (showRequired) {\n <small class=\"text-danger\">{{ 'validation.required' | translate }}</small>\n }\n @if (serverErrors; as errors) {\n @for (error of errors; track error) {\n <small class=\"text-danger block\">{{ error }}</small>\n }\n }\n </div>\n }\n </div>\n } @else {\n <!-- Pill trigger \u2014 filter mode, or field mode with variant=\"pill\" -->\n <button\n type=\"button\"\n class=\"dp__trigger\"\n [class.open]=\"open()\"\n [disabled]=\"isDisabled()\"\n [attr.aria-haspopup]=\"'dialog'\"\n [attr.aria-expanded]=\"open()\"\n [attr.aria-label]=\"mode() === 'field' && !(labelKey() || label()) ? (ariaLabelKey() ? (ariaLabelKey() | translate) : (ariaLabel() || null)) : null\"\n (click)=\"toggle($event)\"\n >\n <i class=\"ico pi pi-calendar\" aria-hidden=\"true\"></i>\n @if (mode() === 'field') {\n <span class=\"val\">\n {{ hasFieldValue() ? fieldDisplay() : (placeholderKey() ? (placeholderKey() | translate) : placeholder()) }}\n </span>\n } @else {\n <span class=\"lbl\">\n {{ triggerLabelKey() ? (triggerLabelKey() | translate) : triggerLabel() }}\n </span>\n <span class=\"val\">\n @if (resolvedRange().labelKey; as key) {\n {{ key | translate }}\n } @else {\n {{ resolvedRange().label }}\n }\n </span>\n }\n <svg class=\"caret ef-chevron\" width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><polyline points=\"6 9 12 15 18 9\" /></svg>\n </button>\n }\n</div>\n\n<!--\n Popover panel \u2014 instantiated via ViewContainerRef + appended to\n <body> when `open()` flips on. Anchored absolutely to the trigger\n via positionPanel(); escapes ancestor overflow / stacking contexts.\n-->\n<ng-template #panelTpl>\n <div\n class=\"dp__panel open\"\n [class.wide]=\"view() === 'custom'\"\n role=\"dialog\"\n [attr.aria-label]=\"triggerLabelKey() | translate\"\n >\n <!-- \u2500\u2500 View 1 \u2014 preset list \u2500\u2500 -->\n <div class=\"dp__view\" [class.active]=\"view() === 'presets'\">\n @for (preset of effectivePresets(); track trackByPreset($index, preset)) {\n <button\n type=\"button\"\n class=\"dp__preset\"\n [class.active]=\"isActivePreset(preset.key)\"\n (click)=\"selectPreset(preset)\"\n >\n {{ preset.labelKey ? (preset.labelKey | translate) : preset.label }}\n <span class=\"hint\">{{ presetHint(preset) }}</span>\n </button>\n }\n\n @if (allowCustomRange()) {\n <div class=\"dp__divider\" role=\"separator\"></div>\n <button\n type=\"button\"\n class=\"dp__preset muted\"\n (click)=\"openCustomView()\"\n >\n <span style=\"display:inline-flex;align-items:center;gap:8px\">\n <i class=\"ico-lead pi pi-calendar\" aria-hidden=\"true\"></i>\n {{ 'date_preset_custom' | translate }}\n </span>\n <span class=\"hint\">\u21E7</span>\n </button>\n }\n </div>\n\n <!-- \u2500\u2500 View 2 \u2014 custom range with calendar \u2500\u2500 -->\n <div class=\"dp__view\" [class.active]=\"view() === 'custom'\">\n <div class=\"dp__custom-head\">\n @if (mode() !== 'field') {\n <button\n type=\"button\"\n class=\"dp__back\"\n [attr.aria-label]=\"'common_back' | translate\"\n (click)=\"backToPresets()\"\n >\n <i class=\"pi pi-chevron-left\" aria-hidden=\"true\"></i>\n </button>\n } @else {\n <span></span>\n }\n <div class=\"dp__custom-title\">\n <span class=\"pulse-dot\"></span>\n {{ (mode() === 'field' && selectionMode() === 'single' ? 'date_select_date' : 'date_custom_range') | translate }}\n </div>\n <span></span>\n </div>\n\n <div class=\"dp__cal\">\n <div class=\"dp__cal-head\">\n <button\n type=\"button\"\n class=\"dp__cal-nav\"\n [attr.aria-label]=\"'date_prev_month' | translate\"\n (click)=\"headerPrev()\"\n >\n <i class=\"pi pi-chevron-left\" aria-hidden=\"true\"></i>\n </button>\n <div class=\"dp__cal-title\">\n @switch (calMode()) {\n @case ('years') {\n <span class=\"dp__cal-pick is-static\">{{ yearWindowLabel() }}</span>\n }\n @case ('months') {\n <button type=\"button\" class=\"dp__cal-pick\" (click)=\"openYearPicker()\">\n {{ yearLabel() }}\n </button>\n }\n @default {\n <button type=\"button\" class=\"dp__cal-pick\" (click)=\"openMonthPicker()\">\n {{ monthLabel() }}\n </button>\n <button type=\"button\" class=\"dp__cal-pick\" (click)=\"openYearPicker()\">\n {{ yearLabel() }}\n </button>\n }\n }\n </div>\n <button\n type=\"button\"\n class=\"dp__cal-nav\"\n [attr.aria-label]=\"'date_next_month' | translate\"\n (click)=\"headerNext()\"\n >\n <i class=\"pi pi-chevron-right\" aria-hidden=\"true\"></i>\n </button>\n </div>\n\n @switch (calMode()) {\n @case ('months') {\n <div class=\"dp__cal-cells\">\n @for (cell of monthCells(); track cell.month) {\n <button\n type=\"button\"\n class=\"dp__cal-cell\"\n [class.active]=\"cell.active\"\n [class.today]=\"cell.today\"\n (click)=\"selectMonth(cell.month)\"\n >\n {{ cell.label }}\n </button>\n }\n </div>\n }\n @case ('years') {\n <div class=\"dp__cal-cells\">\n @for (cell of yearCells(); track cell.year) {\n <button\n type=\"button\"\n class=\"dp__cal-cell\"\n [class.active]=\"cell.active\"\n [class.today]=\"cell.today\"\n (click)=\"selectYear(cell.year)\"\n >\n {{ cell.year }}\n </button>\n }\n </div>\n }\n @default {\n <div class=\"dp__cal-dow-row\">\n @for (label of dowLabels(); track trackByDow($index, label)) {\n <div class=\"dp__cal-dow\">{{ label }}</div>\n }\n </div>\n\n <div class=\"dp__cal-grid\">\n @for (day of calendarGrid(); track trackByDay($index, day)) {\n <button\n type=\"button\"\n class=\"dp__cal-day\"\n [class.out]=\"day.out\"\n [class.today]=\"day.today\"\n [class.in-range]=\"day.inRange\"\n [class.range-start]=\"day.rangeStart\"\n [class.range-end]=\"day.rangeEnd\"\n [attr.aria-label]=\"day.date | date:'fullDate'\"\n (click)=\"selectDay(day.date)\"\n >\n <span class=\"day-num\">{{ day.day }}</span>\n </button>\n }\n </div>\n }\n }\n </div>\n\n @if (mode() === 'field' && selectionMode() === 'single') {\n <!-- Single-date field: date (+ time when enabled). Without time,\n picking a day commits & closes; with time, Apply commits. -->\n <div class=\"dp__inputs\" [class.dp__inputs--single]=\"!showTime()\">\n <div class=\"dp__field\">\n <label [attr.for]=\"effectiveId + '-single'\">{{ 'date_select_date' | translate }}</label>\n <input\n #singleInput\n [id]=\"effectiveId + '-single'\"\n type=\"text\"\n inputmode=\"numeric\"\n maxlength=\"10\"\n [value]=\"fromText()\"\n placeholder=\"JJ/MM/AAAA\"\n (keydown)=\"onDateKeydown($event, singleInput)\"\n (change)=\"commitSingleInput(singleInput)\"\n />\n </div>\n @if (showTime()) {\n <div class=\"dp__field\">\n <label [attr.for]=\"effectiveId + '-single-time'\">{{ 'date_time' | translate }}</label>\n <input\n #singleTime\n [id]=\"effectiveId + '-single-time'\"\n type=\"text\"\n inputmode=\"numeric\"\n maxlength=\"5\"\n [value]=\"fromTimeText()\"\n placeholder=\"HH:mm\"\n (keydown)=\"onTimeKeydown($event, singleTime)\"\n (change)=\"commitTimeInput('start', singleTime)\"\n />\n </div>\n }\n </div>\n } @else {\n <div class=\"dp__inputs\">\n <div class=\"dp__field\">\n <label [attr.for]=\"effectiveId + '-from'\">{{ 'date_from' | translate }}</label>\n <input\n #fromInput\n [id]=\"effectiveId + '-from'\"\n type=\"text\"\n inputmode=\"numeric\"\n maxlength=\"10\"\n [value]=\"fromText()\"\n placeholder=\"JJ/MM/AAAA\"\n (keydown)=\"onDateKeydown($event, fromInput)\"\n (change)=\"commitDateInput('start', fromInput)\"\n />\n @if (mode() === 'field' && showTime()) {\n <input\n #fromTime\n [attr.aria-label]=\"'date_time' | translate\"\n type=\"text\"\n inputmode=\"numeric\"\n maxlength=\"5\"\n [value]=\"fromTimeText()\"\n placeholder=\"HH:mm\"\n (keydown)=\"onTimeKeydown($event, fromTime)\"\n (change)=\"commitTimeInput('start', fromTime)\"\n />\n }\n </div>\n <div class=\"dp__field\">\n <label [attr.for]=\"effectiveId + '-to'\">{{ 'date_to' | translate }}</label>\n <input\n #toInput\n [id]=\"effectiveId + '-to'\"\n type=\"text\"\n inputmode=\"numeric\"\n maxlength=\"10\"\n [value]=\"toText()\"\n placeholder=\"JJ/MM/AAAA\"\n (keydown)=\"onDateKeydown($event, toInput)\"\n (change)=\"commitDateInput('end', toInput)\"\n />\n @if (mode() === 'field' && showTime()) {\n <input\n #toTime\n [attr.aria-label]=\"'date_time' | translate\"\n type=\"text\"\n inputmode=\"numeric\"\n maxlength=\"5\"\n [value]=\"toTimeText()\"\n placeholder=\"HH:mm\"\n (keydown)=\"onTimeKeydown($event, toTime)\"\n (change)=\"commitTimeInput('end', toTime)\"\n />\n }\n </div>\n </div>\n }\n\n <!-- Footer: hidden only for single field WITHOUT time (commit on pick). -->\n @if (!(mode() === 'field' && selectionMode() === 'single' && !showTime())) {\n <div class=\"dp__actions\">\n @if (mode() === 'field' && selectionMode() === 'single') {\n <span></span>\n } @else {\n <span class=\"duration\">\n <span class=\"num\">{{ draftDurationDays() }}</span>\n {{ 'date_days' | translate }}\n </span>\n }\n <div class=\"right\">\n @if (mode() !== 'field') {\n <button\n type=\"button\"\n class=\"btn btn-ghost btn-sm\"\n (click)=\"backToPresets()\"\n >\n {{ 'common_back' | translate }}\n </button>\n }\n <button\n type=\"button\"\n class=\"btn btn-primary btn-sm\"\n [disabled]=\"!draftStart()\"\n (click)=\"applyCustomRange()\"\n >\n {{ 'common_apply' | translate }}\n </button>\n </div>\n </div>\n }\n </div>\n </div>\n</ng-template>\n", styles: ["@charset \"UTF-8\";:host{display:inline-block}:host(.dp-host--field){display:block}.hint{unicode-bidi:plaintext}\n"] }]
|
|
2135
2144
|
}], ctorParameters: () => [], propDecorators: { anchorEl: [{
|
|
2136
2145
|
type: ViewChild,
|
|
2137
2146
|
args: ['anchorEl', { static: true }]
|
|
@@ -3774,27 +3783,43 @@ class EfOrderBuilderComponent {
|
|
|
3774
3783
|
const lines = this.orderLines();
|
|
3775
3784
|
const labels = this.countGroupLabels();
|
|
3776
3785
|
const countMap = new Map();
|
|
3786
|
+
// Group on a normalised key. The catalogue stores the same format
|
|
3787
|
+
// under more than one spelling — one live order carries both
|
|
3788
|
+
// `PF-30ml-F-TRANSPARENT` and `PF-30ML-F-TRANSPARENT` — and this
|
|
3789
|
+
// function used to key the map on the raw string while matching
|
|
3790
|
+
// labels with `.toUpperCase()`. Two keys then resolved to one label
|
|
3791
|
+
// and the panel printed that format twice, splitting 119 units into
|
|
3792
|
+
// a row of 112 and a row of 7. Whoever read it undercounted.
|
|
3793
|
+
const normalise = (code) => code.trim().toUpperCase();
|
|
3794
|
+
// Keep the first spelling seen, so the row still carries a real code
|
|
3795
|
+
// for anything downstream that wants one.
|
|
3796
|
+
const originalByKey = new Map();
|
|
3777
3797
|
lines.forEach((line) => {
|
|
3778
3798
|
const product = line.product;
|
|
3779
3799
|
const countGroup = line.countGroup || product?.countGroup;
|
|
3780
3800
|
if (countGroup) {
|
|
3781
|
-
const
|
|
3782
|
-
|
|
3801
|
+
const key = normalise(countGroup);
|
|
3802
|
+
if (!originalByKey.has(key))
|
|
3803
|
+
originalByKey.set(key, countGroup);
|
|
3804
|
+
countMap.set(key, (countMap.get(key) || 0) + (line.quantity || 0));
|
|
3783
3805
|
}
|
|
3784
3806
|
});
|
|
3785
3807
|
const items = [];
|
|
3786
|
-
countMap.forEach((count,
|
|
3787
|
-
const labelItem = labels.find((l) => l.code
|
|
3808
|
+
countMap.forEach((count, key) => {
|
|
3809
|
+
const labelItem = labels.find((l) => normalise(l.code) === key);
|
|
3788
3810
|
items.push({
|
|
3789
|
-
code,
|
|
3790
|
-
label: labelItem?.label ||
|
|
3811
|
+
code: originalByKey.get(key) ?? key,
|
|
3812
|
+
label: labelItem?.label || originalByKey.get(key) || key,
|
|
3791
3813
|
count,
|
|
3792
3814
|
column: labelItem?.column ?? 1,
|
|
3793
3815
|
});
|
|
3794
3816
|
});
|
|
3795
3817
|
items.sort((a, b) => {
|
|
3796
|
-
|
|
3797
|
-
|
|
3818
|
+
// Same normalisation as above: this used to be a case-SENSITIVE
|
|
3819
|
+
// compare sitting two lines below a case-insensitive one, so a
|
|
3820
|
+
// differently-cased code fell through to sortOrder 999.
|
|
3821
|
+
const labelA = labels.find((l) => normalise(l.code) === normalise(a.code));
|
|
3822
|
+
const labelB = labels.find((l) => normalise(l.code) === normalise(b.code));
|
|
3798
3823
|
const orderA = labelA?.sortOrder ?? 999;
|
|
3799
3824
|
const orderB = labelB?.sortOrder ?? 999;
|
|
3800
3825
|
return orderA - orderB;
|
|
@@ -4088,7 +4113,7 @@ class EfOrderBuilderComponent {
|
|
|
4088
4113
|
return OrderEntityHelper.calculateTotals(this.order());
|
|
4089
4114
|
}
|
|
4090
4115
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfOrderBuilderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4091
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfOrderBuilderComponent, isStandalone: true, selector: "ef-order-builder", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, order: { classPropertyName: "order", publicName: "order", isSignal: true, isRequired: true, transformFunction: null }, products: { classPropertyName: "products", publicName: "products", isSignal: true, isRequired: true, transformFunction: null }, customers: { classPropertyName: "customers", publicName: "customers", isSignal: true, isRequired: true, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, countGroupLabels: { classPropertyName: "countGroupLabels", publicName: "countGroupLabels", isSignal: true, isRequired: false, transformFunction: null }, productKeyField: { classPropertyName: "productKeyField", publicName: "productKeyField", isSignal: true, isRequired: false, transformFunction: null }, backendErrors: { classPropertyName: "backendErrors", publicName: "backendErrors", isSignal: true, isRequired: false, transformFunction: null }, auditEntries: { classPropertyName: "auditEntries", publicName: "auditEntries", isSignal: true, isRequired: false, transformFunction: null }, focusMode: { classPropertyName: "focusMode", publicName: "focusMode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { order: "orderChange", openCatalogue: "openCatalogue", orderChanged: "orderChanged", orderValidated: "orderValidated", productsAdded: "productsAdded", focusMode: "focusModeChange" }, viewQueries: [{ propertyName: "typeahead", first: true, predicate: EfProductTypeaheadComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<div\n class=\"order-builder\"\n [class.ob-collapsed]=\"focusMode()\"\n [class.ob-rail-open]=\"railOpen()\">\n <div class=\"ob-grid\">\n <div class=\"ob-main\">\n <!-- Order header: customer + date + express add (one card) -->\n <ef-card\n titleKey=\"order_builder.section_order\"\n [metaKey]=\"readonly() ? '' : 'order_builder.quick_add_hint'\"\n [overflowVisible]=\"true\">\n <div class=\"ob-order-head\">\n <div class=\"form-grid\">\n <div class=\"field col-6\">\n <ef-label labelKey=\"label.customer\" for=\"ob-cust\" [required]=\"true\" />\n <ef-select\n inputId=\"ob-cust\"\n [options]=\"customers()\"\n optionLabel=\"companyName\"\n optionValue=\"customerId\"\n [ngModel]=\"order().customerId\"\n (ngModelChange)=\"updateCustomer($event)\"\n [disabled]=\"readonly()\"\n [showClear]=\"true\"\n [filter]=\"true\"\n filterBy=\"companyName\"\n filterPlaceholderKey=\"label.search_customer\"\n appendTo=\"body\"\n placeholderKey=\"label.select_customer\" />\n </div>\n <div class=\"field col-3\">\n <ef-label labelKey=\"label.order_date\" for=\"ob-date\" [required]=\"true\" />\n <ef-datepicker-advanced\n mode=\"field\"\n selectionMode=\"single\"\n inputId=\"ob-date\"\n placeholderKey=\"date_select_date\"\n [ngModel]=\"order().orderDate\"\n (ngModelChange)=\"updateOrderDate($event)\"\n [disabled]=\"readonly()\" />\n </div>\n </div>\n\n @if (!readonly()) {\n <div class=\"qa-grid\">\n <div class=\"qa-combo-wrap\">\n <ef-label labelKey=\"label.article\" />\n <ef-product-typeahead\n [products]=\"products()\"\n [productKeyField]=\"productKeyField()\"\n placeholderKey=\"label.search_product\"\n [currency]=\"currency()\"\n [locale]=\"locale()\"\n (productSelect)=\"onTypeaheadSelect($event)\"\n [showVariants]=\"false\" />\n </div>\n <div class=\"qa-price\">\n <ef-label labelKey=\"label.sale_price\" />\n <ef-inputnumber\n variant=\"comptoir\"\n [value]=\"quickAddPrice()\"\n (valueChangeEvent)=\"quickAddPrice.set($event ?? 0)\"\n [minFractionDigits]=\"2\"\n [maxFractionDigits]=\"2\"\n [locale]=\"locale()\"\n [disabled]=\"readonly()\" />\n </div>\n <div class=\"qa-qtyf\">\n <ef-label labelKey=\"label.quantity\" />\n <ef-inputnumber\n variant=\"comptoir\"\n [ngModel]=\"quickAddQuantity()\"\n (ngModelChange)=\"quickAddQuantity.set($event)\"\n [min]=\"0\"\n [maxFractionDigits]=\"4\"\n [locale]=\"locale()\"\n [disabled]=\"readonly()\"\n />\n </div>\n <div class=\"qa-go\">\n <ef-button\n labelKey=\"action.add\"\n icon=\"pi pi-plus\"\n variant=\"comptoir\"\n severity=\"tenant\"\n [disabled]=\"quickAddDisabled()\"\n (clickEvent)=\"addQuickProduct()\" />\n </div>\n </div>\n }\n </div>\n </ef-card>\n\n <!-- Lines -->\n <ef-card titleKey=\"order_builder.lines\" [meta]=\"'\u00B7 ' + orderLines().length\">\n <div head-extra class=\"head-actions\">\n @if (config().showCatalogue && !readonly()) {\n <ef-button\n labelKey=\"action.open_catalogue\"\n icon=\"pi pi-box\"\n severity=\"secondary\"\n [text]=\"true\"\n (clickEvent)=\"handleOpenCatalogue()\" />\n }\n <ef-button\n [labelKey]=\"focusMode() ? 'order_builder.show_summary' : 'order_builder.fullscreen'\"\n icon=\"pi pi-window-maximize\"\n severity=\"secondary\"\n [text]=\"true\"\n (clickEvent)=\"toggleFocus()\" />\n </div>\n\n <!-- The lines table carries seven columns of numbers and cannot\n usefully shrink to 390px, so it scrolls inside its own box\n rather than widening the page. It had no scroller at all: at\n 390px the table measured 540px and was simply clipped. -->\n <div class=\"ob-tbl-scroll\">\n <table class=\"ob-tbl\">\n <thead>\n <tr>\n <th class=\"col-prod\">{{ 'label.article' | translate }}</th>\n <th>{{ 'label.sale_price' | translate }}</th>\n <th>{{ 'label.quantity' | translate }}</th>\n @if (config().enableDiscount) {\n <th>{{ 'label.discount' | translate }}</th>\n }\n @if (config().enableTax) {\n <th>{{ 'order_builder.tax' | translate }}</th>\n }\n <th>{{ 'label.subtotal' | translate }}</th>\n <th class=\"col-act\"></th>\n </tr>\n </thead>\n <tbody>\n @for (item of orderLines(); track item.id; let i = $index) {\n <tr>\n <td class=\"col-prod\">\n <div class=\"p\">\n <span class=\"ln\">{{ i + 1 }}</span>\n <span class=\"pp\">\n <span class=\"nm\">{{ item.productDescription }}</span>\n </span>\n </div>\n </td>\n <td class=\"num w-[120px]\">\n @if (!readonly() && isEditingCell(item, 'price')) {\n <ef-inputnumber\n [(ngModel)]=\"item.productUnitPrice\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [minFractionDigits]=\"2\"\n [maxFractionDigits]=\"2\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button\n type=\"button\"\n class=\"ln-val\"\n [disabled]=\"readonly()\"\n (click)=\"startEditCell(item, 'price')\">\n {{ item.productUnitPrice | number: '1.2-2' : locale() }}\n </button>\n }\n </td>\n <td class=\"num w-[120px]\">\n @if (!readonly() && isEditingCell(item, 'qty')) {\n <ef-inputnumber\n [(ngModel)]=\"item.quantity\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [maxFractionDigits]=\"4\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button\n type=\"button\"\n class=\"ln-val\"\n [disabled]=\"readonly()\"\n (click)=\"startEditCell(item, 'qty')\">\n {{ item.quantity | number: '1.0-4' : locale() }}\n </button>\n }\n </td>\n @if (config().enableDiscount) {\n <td class=\"num w-[80px]\">\n @if (!readonly() && isEditingCell(item, 'discount')) {\n <ef-inputnumber\n [(ngModel)]=\"item.discount\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [max]=\"100\"\n [maxFractionDigits]=\"2\"\n suffix=\" %\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button\n type=\"button\"\n class=\"ln-val\"\n [disabled]=\"readonly()\"\n (click)=\"startEditCell(item, 'discount')\">\n {{ item.discount | number: '1.0-2' : locale() }} %\n </button>\n }\n </td>\n }\n @if (config().enableTax) {\n <td class=\"num w-[80px]\">\n @if (!readonly() && isEditingCell(item, 'tva')) {\n <ef-inputnumber\n [(ngModel)]=\"item.taxRate\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [max]=\"100\"\n [maxFractionDigits]=\"2\"\n suffix=\" %\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button\n type=\"button\"\n class=\"ln-val\"\n [disabled]=\"readonly()\"\n (click)=\"startEditCell(item, 'tva')\">\n {{ (item.taxRate ?? 0) | number: '1.0-2' : locale() }} %\n </button>\n }\n </td>\n }\n <td class=\"total\">\n {{ calculateSubtotal(item) | number: '1.2-2' : locale() }}\n </td>\n <td class=\"col-act\">\n @if (!readonly()) {\n <ef-button\n icon=\"pi pi-trash\"\n severity=\"danger\"\n [text]=\"true\"\n styleClass=\"ob-del\"\n (clickEvent)=\"removeOrderLine(item.id)\" />\n }\n </td>\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"colCount()\">\n <div class=\"ob-empty\">{{ 'order_builder.empty' | translate }}</div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </ef-card>\n </div>\n\n <!-- CONTEXT RAIL (1/3) -->\n <aside class=\"ob-rail\">\n <!-- Totaux (top) -->\n <ef-card titleKey=\"order_builder.totals\" [meta]=\"currency()\">\n <div class=\"ob-totals\">\n <div class=\"row\">\n <span class=\"lbl\">{{ 'order_builder.gross' | translate }}</span>\n <span class=\"v\">{{ grossTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n <div class=\"row discount\">\n <span class=\"lbl\">{{ 'order_builder.discount' | translate }}</span>\n <span class=\"v\">\u2212{{ discountTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n <div class=\"row\">\n <span class=\"lbl\">{{ 'order_builder.tax' | translate }}</span>\n <span class=\"v\">{{ tvaTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n <hr />\n <div class=\"row grand\">\n <span class=\"lbl\">{{ 'order_builder.grand_total' | translate }}</span>\n <span class=\"v\">{{ finalTotal() | number: '1.2-2' : locale() }}<span class=\"cur\">{{ currency() }}</span></span>\n </div>\n </div>\n </ef-card>\n\n <!-- R\u00E9capitulatif par format -->\n @if (productsSummary().items.length > 0) {\n <ef-card titleKey=\"order_builder.recap\" metaKey=\"order_builder.auto\">\n <div class=\"ob-recap\">\n <div class=\"cols\">\n @for (col of recapColumns(); track $index) {\n <div class=\"grp\">\n @for (seg of col; track seg.code) {\n <div class=\"seg\">\n <span class=\"fmt\">{{ seg.label }}</span>\n <span class=\"n\">{{ seg.count | number: '1.0-0' : locale() }}</span>\n </div>\n }\n </div>\n }\n </div>\n <div class=\"tot\">\n <span class=\"l\">{{ 'order_builder.total_units' | translate }}</span>\n <span class=\"n\">{{ productsSummary().totalCount | number: '1.0-0' : locale() }}</span>\n </div>\n </div>\n </ef-card>\n }\n\n <!-- Historique (optional, component-owned) -->\n @if (hasAudit()) {\n <ef-change-history [entries]=\"auditEntries()\" />\n }\n </aside>\n </div>\n\n <!-- Floating summary FAB + backdrop -->\n <button type=\"button\" class=\"ob-backdrop\" aria-hidden=\"true\" tabindex=\"-1\" (click)=\"toggleRail(false)\"></button>\n <div class=\"ob-fab\" role=\"status\">\n <div class=\"ft\">\n <span class=\"k\">{{ 'order_builder.total_units' | translate }} \u00B7 {{ productsSummary().totalCount }}</span>\n <span class=\"vv\">{{ finalTotal() | number: '1.2-2' : locale() }}<span class=\"cur\">{{ currency() }}</span></span>\n </div>\n <button type=\"button\" class=\"open\" (click)=\"toggleRail(true)\">\n {{ 'order_builder.summary' | translate }}\n </button>\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block}:host .order-builder{display:grid;gap:var(--s-3)}:host .order-builder .head-actions{display:flex;align-items:center;gap:6px}:host .order-builder .ob-empty{padding:28px 16px;text-align:center;color:var(--text-mute);font-size:13px}:host .order-builder .text-mute{color:var(--text-mute);font-weight:600}:host .order-builder .form-grid{display:grid;gap:var(--s-3);grid-template-columns:repeat(12,minmax(0,1fr))}:host .order-builder .form-grid>.col-3{grid-column:span 3}:host .order-builder .form-grid>.col-4{grid-column:span 4}:host .order-builder .form-grid>.col-5{grid-column:span 5}:host .order-builder .form-grid>.col-6{grid-column:span 6}@media(max-width:767px){:host .order-builder .form-grid>[class^=col-]{grid-column:span 12}}:host .order-builder .field{display:grid;gap:4px}:host .order-builder .field>label{font-size:11px;font-weight:700;color:var(--text-mute);text-transform:uppercase;letter-spacing:.06em}:host .order-builder .field .req{color:var(--st-cancelled-fg);margin-inline-start:2px}:host .order-builder .ob-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--s-3);align-items:start}@media(min-width:1280px){:host .order-builder .ob-grid{grid-template-columns:minmax(0,2fr) minmax(0,1fr)}}:host .order-builder .ob-main{display:grid;gap:var(--s-3);min-width:0}:host .order-builder .ob-rail{display:grid;gap:var(--s-3);align-content:start}@media(min-width:1280px){:host .order-builder .ob-rail{position:sticky;top:44px}}:host .order-builder .ob-order-head{display:grid;gap:var(--s-4)}:host .order-builder .qa-grid{display:flex;flex-wrap:wrap;gap:10px;align-items:end;padding-top:var(--s-3);border-top:1px solid var(--rule-soft)}:host .order-builder .qa-combo-wrap{flex:1 1 260px;min-width:0}:host .order-builder .qa-price{flex:0 1 132px}:host .order-builder .qa-qtyf{flex:0 0 132px}:host .order-builder .qa-go{flex:0 0 auto}@media(max-width:560px){:host .order-builder .qa-combo-wrap,:host .order-builder .qa-price,:host .order-builder .qa-qtyf,:host .order-builder .qa-go{flex:1 1 100%}}:host .order-builder .qin{width:58px;text-align:end;font:inherit;font-family:var(--font-mono);font-weight:700;padding:6px 8px;min-height:34px;border:1px solid var(--rule);border-radius:var(--r-sm);background:var(--paper-alt);color:var(--text);outline:0;-moz-appearance:textfield}:host .order-builder .qin::-webkit-outer-spin-button,:host .order-builder .qin::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}:host .order-builder .qin:focus{border-color:var(--tenant-400);box-shadow:0 0 0 3px var(--tenant-100)}:host .order-builder .ob-tbl-scroll{min-width:0;max-width:100%;overflow-x:auto;overscroll-behavior-x:contain}:host .order-builder .ob-tbl{margin-inline:-16px;border-collapse:separate;border-spacing:0;font-size:13px}:host .order-builder .ob-tbl thead th:first-child,:host .order-builder .ob-tbl tbody td:first-child{padding-inline-start:16px}:host .order-builder .ob-tbl thead th:last-child,:host .order-builder .ob-tbl tbody td:last-child{padding-inline-end:16px}:host .order-builder .ob-tbl thead th{position:sticky;top:0;z-index:2;background:var(--paper);text-align:end;padding:8px 10px;font-size:10px;text-transform:uppercase;letter-spacing:.06em;font-weight:800;color:var(--text-mute);white-space:nowrap;box-shadow:inset 0 -1px 0 var(--rule)}:host .order-builder .ob-tbl thead th.col-prod{text-align:start}:host .order-builder .ob-tbl thead th.col-act{width:42px}:host .order-builder .ob-tbl tbody td{padding:7px 10px;text-align:end;border-bottom:1px solid var(--rule-soft);vertical-align:middle;white-space:nowrap}:host .order-builder .ob-tbl tbody tr:hover td{background:var(--paper)}:host .order-builder .ob-tbl .col-prod{text-align:start;min-width:186px}@media(max-width:767px){:host .order-builder .ob-tbl .col-prod{min-width:168px}}:host .order-builder .ob-tbl .p{display:flex;align-items:baseline;gap:9px;line-height:1.25}:host .order-builder .ob-tbl .p .ln{flex:0 0 auto;min-width:15px;text-align:end;font-family:var(--font-mono);font-size:10.5px;font-weight:700;color:var(--text-soft)}:host .order-builder .ob-tbl .p .pp{display:grid;gap:1px;min-width:0}:host .order-builder .ob-tbl .p .nm{font-weight:600;letter-spacing:-.005em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .order-builder .ob-tbl .p .nm em{font-style:normal;color:var(--text-mute);font-weight:600}:host .order-builder .ob-tbl .p .ref{font-family:var(--font-mono);font-size:10.5px;color:var(--text-soft);font-weight:600}:host .order-builder .ob-tbl .num,:host .order-builder .ob-tbl .total{font-family:var(--font-mono);font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums}:host .order-builder .ob-tbl .ln-val{appearance:none;width:100%;margin:0;padding:5px 8px;border:1px solid transparent;border-radius:7px;background:transparent;font:inherit;font-family:var(--font-mono);font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums;color:inherit;text-align:end;cursor:text;transition:background .12s ease,border-color .12s ease}:host .order-builder .ob-tbl .ln-val:hover:not(:disabled){background:var(--paper);border-color:var(--rule)}:host .order-builder .ob-tbl .ln-val:focus-visible{outline:none;background:var(--paper);border-color:var(--tenant, var(--rule))}:host .order-builder .ob-tbl .ln-val:disabled{cursor:default;background:transparent;border-color:transparent}:host .order-builder .ob-tbl .total{font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:13.5px}:host .order-builder .ob-tbl .cur{font-size:9.5px;color:var(--text-soft);margin-inline-start:3px}:host .order-builder .ob-tbl .ed{cursor:text;padding:4px 7px;border-radius:6px;border:1px solid transparent;display:inline-block;font-family:var(--font-mono)}:host .order-builder .ob-tbl .ed:hover{background:var(--paper-alt);border-color:var(--rule)}:host .order-builder .ob-tbl .free{color:var(--st-delivered-fg)}:host .order-builder .ob-del{color:var(--text-soft)}:host .order-builder .ob-del:hover{color:var(--st-cancelled-fg);border-color:var(--st-cancelled-bg)}:host .order-builder .ob-totals{display:grid;gap:6px}:host .order-builder .ob-totals .row{display:flex;justify-content:space-between;align-items:baseline;gap:14px}:host .order-builder .ob-totals .row .lbl{font-size:12px;font-weight:600;color:var(--text-mute)}:host .order-builder .ob-totals .row .v{font-family:var(--font-mono);font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums;font-weight:700;font-size:12.5px}:host .order-builder .ob-totals .row.discount .v{color:var(--st-pending-fg)}:host .order-builder .ob-totals hr{border:0;border-top:1px solid var(--rule);margin:5px 0}:host .order-builder .ob-totals .grand .lbl{font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:13.5px;color:var(--text)}:host .order-builder .ob-totals .grand .v{font-family:var(--font-display);font-variation-settings:\"opsz\" 96,\"wght\" 700,\"wdth\" 80;font-size:26px;letter-spacing:-.03em;color:var(--text)}:host .order-builder .ob-totals .grand .cur{font-family:var(--font-mono);font-size:.4em;color:var(--text-mute);margin-inline-start:4px}:host .order-builder .ob-recap .cols{display:grid;grid-template-columns:1fr 1fr}:host .order-builder .ob-recap .grp{display:flex;flex-direction:column;gap:3px;min-width:0}:host .order-builder .ob-recap .grp:first-child{padding-inline-end:14px}:host .order-builder .ob-recap .grp+.grp{padding-inline-start:14px;border-inline-start:1px solid var(--rule)}:host .order-builder .ob-recap .seg{display:flex;justify-content:space-between;align-items:baseline;gap:8px}:host .order-builder .ob-recap .seg .fmt{font-size:11.5px;font-weight:700;color:var(--text);letter-spacing:-.005em;min-width:0}:host .order-builder .ob-recap .seg .fmt:after{content:\":\";color:var(--text-soft)}:host .order-builder .ob-recap .seg .n{font-family:var(--font-mono);font-weight:700;font-size:12px;color:var(--text);font-variant-numeric:tabular-nums;flex-shrink:0}:host .order-builder .ob-recap .tot{display:flex;justify-content:space-between;align-items:baseline;margin-top:12px;padding-top:10px;border-top:1px solid var(--rule)}:host .order-builder .ob-recap .tot .l{font-weight:700;font-size:12px}:host .order-builder .ob-recap .tot .n{font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:18px;font-variant-numeric:tabular-nums}:host .order-builder .ob-rail-close{display:none}:host .order-builder .ob-fab{display:none;position:fixed;inset-inline-end:20px;bottom:20px;z-index:30;align-items:center;gap:10px;background:var(--ink-active);color:var(--paper);border-radius:var(--r-pill);padding:8px 10px 8px 16px;box-shadow:var(--shadow-3)}:host .order-builder .ob-fab .ft{display:grid;line-height:1.1}:host .order-builder .ob-fab .ft .k{font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;opacity:.65}:host .order-builder .ob-fab .ft .vv{font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 82;font-size:16px}:host .order-builder .ob-fab .ft .vv .cur{font-family:var(--font-mono);font-size:.6em;opacity:.7;margin-inline-start:3px}:host .order-builder .ob-fab .open{appearance:none;cursor:pointer;border:0;background:#ffffff24;color:#fff;font:inherit;font-size:12px;font-weight:700;border-radius:var(--r-pill);padding:8px 14px;display:inline-flex;align-items:center;gap:6px}:host .order-builder .ob-fab .open:hover{background:#ffffff3d}:host .order-builder .ob-backdrop{display:none;position:fixed;inset:0;z-index:38;background:#0f11156b;-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px)}:host .order-builder.ob-collapsed .ob-grid{grid-template-columns:minmax(0,1fr)}:host .order-builder.ob-collapsed .ob-fab{display:flex}:host .order-builder.ob-collapsed .ob-rail{position:fixed;top:0;inset-inline-end:0;height:100dvh;width:min(360px,92vw);z-index:40;background:var(--paper);border-inline-start:1px solid var(--rule);box-shadow:var(--shadow-3);padding:14px;overflow:auto;gap:var(--s-3);transform:translate(101%);transition:transform var(--t-base) var(--ease-out)}[dir=rtl] :host .order-builder.ob-collapsed .ob-rail{transform:translate(-101%)}:host .order-builder.ob-collapsed .ob-rail-close{display:inline-flex}:host .order-builder.ob-collapsed.ob-rail-open .ob-rail{transform:none}:host .order-builder.ob-collapsed.ob-rail-open .ob-backdrop{display:block}@media(max-width:1279px){:host .order-builder .ob-grid{grid-template-columns:minmax(0,1fr)}:host .order-builder .ob-fab{display:flex}:host .order-builder .ob-rail{position:fixed;top:0;inset-inline-end:0;height:100dvh;width:min(360px,92vw);z-index:40;background:var(--paper);border-inline-start:1px solid var(--rule);box-shadow:var(--shadow-3);padding:14px;overflow:auto;gap:var(--s-3);transform:translate(101%);transition:transform var(--t-base) var(--ease-out)}[dir=rtl] :host .order-builder .ob-rail{transform:translate(-101%)}:host .order-builder .ob-rail-close{display:inline-flex!important}:host .order-builder.ob-rail-open .ob-rail{transform:none}:host .order-builder.ob-rail-open .ob-backdrop{display:block}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfSelectComponent, selector: "ef-select", inputs: ["options", "optionLabel", "optionValue", "optionDisabled", "dataKey", "group", "optionGroupLabel", "optionGroupChildren", "size", "fluid", "inline", "appendTo", "multiple", "filter", "filterBy", "filterPlaceholder", "filterPlaceholderKey", "autofocusFilter", "editable", "loading", "virtualScroll", "virtualScrollItemSize", "scrollHeight", "multiDisplay", "showToggleAll", "emptyMessage", "emptyMessageKey", "emptyFilterMessage", "emptyFilterMessageKey", "styleClass", "panelStyleClass", "filterByKeys", "variant", "value"], outputs: ["selectionChangeEvent", "filterChangeEvent", "panelShowEvent", "panelHideEvent"] }, { kind: "component", type: EfDatepickerAdvancedComponent, selector: "ef-datepicker-advanced", inputs: ["triggerLabelKey", "triggerLabel", "presets", "activePreset", "start", "end", "allowCustomRange", "mode", "selectionMode", "variant", "showTime"], outputs: ["rangeChange", "opened", "closed"] }, { kind: "component", type: EfInputNumberComponent, selector: "ef-inputnumber", inputs: ["value", "invalid", "format", "showButtons", "buttonLayout", "incrementButtonClass", "decrementButtonClass", "incrementButtonIcon", "decrementButtonIcon", "prefix", "suffix", "currency", "currencyDisplay", "locale", "localeMatcher", "mode", "useGrouping", "minFractionDigits", "maxFractionDigits", "min", "max", "step", "allowEmpty", "inputSize", "size", "maxlength", "minlength", "pattern", "tabindex", "title", "ariaLabelledBy", "inputStyle", "inputStyleClass", "style", "styleClass", "primeNgVariant", "autofocus", "autocomplete", "fluid", "inline", "variant", "pt", "ptOptions", "dt", "unstyled"], outputs: ["inputEvent", "valueChangeEvent", "focusEvent", "blurEvent", "keyDownEvent", "clearEvent"] }, { kind: "component", type: EfLabelComponent, selector: "ef-label", inputs: ["label", "labelKey", "for", "styleClass", "required"] }, { kind: "component", type: EfButtonComponent, selector: "ef-button", inputs: ["label", "labelKey", "icon", "variant", "severity", "size", "disabled", "rounded", "outlined", "raised", "text", "loading", "styleClass", "type", "badge", "badgeSeverity", "tooltip", "tooltipKey", "tooltipPosition"], outputs: ["clickEvent"] }, { kind: "component", type: EfCardComponent, selector: "ef-card", inputs: ["titleKey", "title", "metaKey", "meta", "tone", "forceHead", "hasFoot", "collapsible", "overflowVisible", "loading", "skeleton", "skeletonRows", "skeletonHeight", "collapsed"], outputs: ["collapsedChange"] }, { kind: "component", type: EfChangeHistoryComponent, selector: "ef-change-history", inputs: ["entries", "titleKey", "emptyKey", "limit", "previewLimit", "collapsible", "startCollapsed"] }, { kind: "component", type: EfProductTypeaheadComponent, selector: "ef-product-typeahead", inputs: ["products", "productKeyField", "placeholderKey", "disabled", "currency", "locale", "showVariants"], outputs: ["productSelect"] }, { kind: "pipe", type: i1$3.DecimalPipe, name: "number" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }] });
|
|
4116
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfOrderBuilderComponent, isStandalone: true, selector: "ef-order-builder", inputs: { config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null }, order: { classPropertyName: "order", publicName: "order", isSignal: true, isRequired: true, transformFunction: null }, products: { classPropertyName: "products", publicName: "products", isSignal: true, isRequired: true, transformFunction: null }, customers: { classPropertyName: "customers", publicName: "customers", isSignal: true, isRequired: true, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, countGroupLabels: { classPropertyName: "countGroupLabels", publicName: "countGroupLabels", isSignal: true, isRequired: false, transformFunction: null }, productKeyField: { classPropertyName: "productKeyField", publicName: "productKeyField", isSignal: true, isRequired: false, transformFunction: null }, backendErrors: { classPropertyName: "backendErrors", publicName: "backendErrors", isSignal: true, isRequired: false, transformFunction: null }, auditEntries: { classPropertyName: "auditEntries", publicName: "auditEntries", isSignal: true, isRequired: false, transformFunction: null }, focusMode: { classPropertyName: "focusMode", publicName: "focusMode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { order: "orderChange", openCatalogue: "openCatalogue", orderChanged: "orderChanged", orderValidated: "orderValidated", productsAdded: "productsAdded", focusMode: "focusModeChange" }, viewQueries: [{ propertyName: "typeahead", first: true, predicate: EfProductTypeaheadComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<div\n class=\"order-builder\"\n [class.ob-collapsed]=\"focusMode()\"\n [class.ob-rail-open]=\"railOpen()\">\n <div class=\"ob-grid\">\n <div class=\"ob-main\">\n <!-- Order header: customer + date + express add (one card) -->\n <ef-card\n titleKey=\"order_builder.section_order\"\n [metaKey]=\"readonly() ? '' : 'order_builder.quick_add_hint'\"\n [overflowVisible]=\"true\">\n <div class=\"ob-order-head\">\n <div class=\"form-grid\">\n <div class=\"field col-6\">\n <ef-label labelKey=\"label.customer\" for=\"ob-cust\" [required]=\"true\" />\n <ef-select\n inputId=\"ob-cust\"\n [options]=\"customers()\"\n optionLabel=\"companyName\"\n optionValue=\"customerId\"\n [ngModel]=\"order().customerId\"\n (ngModelChange)=\"updateCustomer($event)\"\n [disabled]=\"readonly()\"\n [showClear]=\"true\"\n [filter]=\"true\"\n filterBy=\"companyName\"\n filterPlaceholderKey=\"label.search_customer\"\n appendTo=\"body\"\n placeholderKey=\"label.select_customer\" />\n </div>\n <div class=\"field col-3\">\n <ef-label labelKey=\"label.order_date\" for=\"ob-date\" [required]=\"true\" />\n <ef-datepicker-advanced\n mode=\"field\"\n selectionMode=\"single\"\n inputId=\"ob-date\"\n placeholderKey=\"date_select_date\"\n [ngModel]=\"order().orderDate\"\n (ngModelChange)=\"updateOrderDate($event)\"\n [disabled]=\"readonly()\" />\n </div>\n </div>\n\n @if (!readonly()) {\n <div class=\"qa-grid\">\n <div class=\"qa-combo-wrap\">\n <ef-label labelKey=\"label.article\" />\n <ef-product-typeahead\n [products]=\"products()\"\n [productKeyField]=\"productKeyField()\"\n placeholderKey=\"label.search_product\"\n [currency]=\"currency()\"\n [locale]=\"locale()\"\n (productSelect)=\"onTypeaheadSelect($event)\"\n [showVariants]=\"false\" />\n </div>\n <div class=\"qa-price\">\n <ef-label labelKey=\"label.sale_price\" />\n <ef-inputnumber\n variant=\"comptoir\"\n [value]=\"quickAddPrice()\"\n (valueChangeEvent)=\"quickAddPrice.set($event ?? 0)\"\n [minFractionDigits]=\"2\"\n [maxFractionDigits]=\"2\"\n [locale]=\"locale()\"\n [disabled]=\"readonly()\" />\n </div>\n <div class=\"qa-qtyf\">\n <ef-label labelKey=\"label.quantity\" />\n <ef-inputnumber\n variant=\"comptoir\"\n [ngModel]=\"quickAddQuantity()\"\n (ngModelChange)=\"quickAddQuantity.set($event)\"\n [min]=\"0\"\n [maxFractionDigits]=\"4\"\n [locale]=\"locale()\"\n [disabled]=\"readonly()\"\n />\n </div>\n <div class=\"qa-go\">\n <ef-button\n labelKey=\"action.add\"\n icon=\"pi pi-plus\"\n variant=\"comptoir\"\n severity=\"tenant\"\n [disabled]=\"quickAddDisabled()\"\n (clickEvent)=\"addQuickProduct()\" />\n </div>\n </div>\n }\n </div>\n </ef-card>\n\n <!-- Lines -->\n <ef-card titleKey=\"order_builder.lines\" [meta]=\"'\u00B7 ' + orderLines().length\">\n <div head-extra class=\"head-actions\">\n @if (config().showCatalogue && !readonly()) {\n <ef-button\n labelKey=\"action.open_catalogue\"\n icon=\"pi pi-box\"\n severity=\"secondary\"\n [text]=\"true\"\n (clickEvent)=\"handleOpenCatalogue()\" />\n }\n <ef-button\n [labelKey]=\"focusMode() ? 'order_builder.show_summary' : 'order_builder.fullscreen'\"\n icon=\"pi pi-window-maximize\"\n severity=\"secondary\"\n [text]=\"true\"\n (clickEvent)=\"toggleFocus()\" />\n </div>\n\n <!-- The lines table carries seven columns of numbers and cannot\n usefully shrink to 390px, so it scrolls inside its own box\n rather than widening the page. It had no scroller at all: at\n 390px the table measured 540px and was simply clipped. -->\n <div class=\"ob-tbl-scroll\">\n <table class=\"ob-tbl\">\n <thead>\n <tr>\n <th class=\"col-prod\">{{ 'label.article' | translate }}</th>\n <th>{{ 'label.sale_price' | translate }}</th>\n <th>{{ 'label.quantity' | translate }}</th>\n @if (config().enableDiscount) {\n <th>{{ 'label.discount' | translate }}</th>\n }\n @if (config().enableTax) {\n <th>{{ 'order_builder.tax' | translate }}</th>\n }\n <th>{{ 'label.subtotal' | translate }}</th>\n <th class=\"col-act\"></th>\n </tr>\n </thead>\n <tbody>\n @for (item of orderLines(); track item.id; let i = $index) {\n <tr>\n <td class=\"col-prod\">\n <div class=\"p\">\n <span class=\"ln\">{{ i + 1 }}</span>\n <span class=\"pp\">\n <span class=\"nm\">{{ item.productDescription }}</span>\n </span>\n </div>\n </td>\n <td class=\"num w-[120px]\">\n @if (!readonly() && isEditingCell(item, 'price')) {\n <ef-inputnumber\n [(ngModel)]=\"item.productUnitPrice\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [minFractionDigits]=\"2\"\n [maxFractionDigits]=\"2\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button\n type=\"button\"\n class=\"ln-val\"\n [disabled]=\"readonly()\"\n (click)=\"startEditCell(item, 'price')\">\n {{ item.productUnitPrice | number: '1.2-2' : locale() }}\n </button>\n }\n </td>\n <td class=\"num w-[120px]\">\n @if (!readonly() && isEditingCell(item, 'qty')) {\n <ef-inputnumber\n [(ngModel)]=\"item.quantity\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [maxFractionDigits]=\"4\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button\n type=\"button\"\n class=\"ln-val\"\n [disabled]=\"readonly()\"\n (click)=\"startEditCell(item, 'qty')\">\n {{ item.quantity | number: '1.0-4' : locale() }}\n </button>\n }\n </td>\n @if (config().enableDiscount) {\n <td class=\"num w-[80px]\">\n @if (!readonly() && isEditingCell(item, 'discount')) {\n <ef-inputnumber\n [(ngModel)]=\"item.discount\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [max]=\"100\"\n [maxFractionDigits]=\"2\"\n suffix=\" %\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button\n type=\"button\"\n class=\"ln-val\"\n [disabled]=\"readonly()\"\n (click)=\"startEditCell(item, 'discount')\">\n {{ item.discount | number: '1.0-2' : locale() }} %\n </button>\n }\n </td>\n }\n @if (config().enableTax) {\n <td class=\"num w-[80px]\">\n @if (!readonly() && isEditingCell(item, 'tva')) {\n <ef-inputnumber\n [(ngModel)]=\"item.taxRate\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [max]=\"100\"\n [maxFractionDigits]=\"2\"\n suffix=\" %\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button\n type=\"button\"\n class=\"ln-val\"\n [disabled]=\"readonly()\"\n (click)=\"startEditCell(item, 'tva')\">\n {{ (item.taxRate ?? 0) | number: '1.0-2' : locale() }} %\n </button>\n }\n </td>\n }\n <td class=\"total\">\n {{ calculateSubtotal(item) | number: '1.2-2' : locale() }}\n </td>\n <td class=\"col-act\">\n @if (!readonly()) {\n <ef-button\n icon=\"pi pi-trash\"\n severity=\"danger\"\n [text]=\"true\"\n styleClass=\"ob-del\"\n (clickEvent)=\"removeOrderLine(item.id)\" />\n }\n </td>\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"colCount()\">\n <div class=\"ob-empty\">{{ 'order_builder.empty' | translate }}</div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </ef-card>\n </div>\n\n <!-- CONTEXT RAIL (1/3) -->\n <aside class=\"ob-rail\">\n <!-- Totaux (top) -->\n <ef-card titleKey=\"order_builder.totals\" [meta]=\"currency()\">\n <div class=\"ob-totals\">\n <div class=\"row\">\n <span class=\"lbl\">{{ 'order_builder.gross' | translate }}</span>\n <span class=\"v\">{{ grossTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n <div class=\"row discount\">\n <span class=\"lbl\">{{ 'order_builder.discount' | translate }}</span>\n <span class=\"v\">\u2212{{ discountTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n <div class=\"row\">\n <span class=\"lbl\">{{ 'order_builder.tax' | translate }}</span>\n <span class=\"v\">{{ tvaTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n <hr />\n <div class=\"row grand\">\n <span class=\"lbl\">{{ 'order_builder.grand_total' | translate }}</span>\n <span class=\"v\">{{ finalTotal() | number: '1.2-2' : locale() }}<span class=\"cur\">{{ currency() }}</span></span>\n </div>\n </div>\n </ef-card>\n\n <!-- R\u00E9capitulatif par format -->\n @if (productsSummary().items.length > 0) {\n <ef-card titleKey=\"order_builder.recap\" metaKey=\"order_builder.auto\">\n <div class=\"ob-recap\">\n <div class=\"cols\">\n @for (col of recapColumns(); track $index) {\n <div class=\"grp\">\n @for (seg of col; track seg.code) {\n <div class=\"seg\">\n <span class=\"fmt\">{{ seg.label }}</span>\n <span class=\"n\">{{ seg.count | number: '1.0-0' : locale() }}</span>\n </div>\n }\n </div>\n }\n </div>\n <div class=\"tot\">\n <span class=\"l\">{{ 'order_builder.total_units' | translate }}</span>\n <span class=\"n\">{{ productsSummary().totalCount | number: '1.0-0' : locale() }}</span>\n </div>\n </div>\n </ef-card>\n }\n\n <!-- Historique (optional, component-owned) -->\n @if (hasAudit()) {\n <ef-change-history [entries]=\"auditEntries()\" />\n }\n </aside>\n </div>\n\n <!-- Floating summary FAB + backdrop -->\n <button type=\"button\" class=\"ob-backdrop\" aria-hidden=\"true\" tabindex=\"-1\" (click)=\"toggleRail(false)\"></button>\n <div class=\"ob-fab\" role=\"status\">\n <div class=\"ft\">\n <span class=\"k\">{{ 'order_builder.total_units' | translate }} \u00B7 {{ productsSummary().totalCount }}</span>\n <span class=\"vv\">{{ finalTotal() | number: '1.2-2' : locale() }}<span class=\"cur\">{{ currency() }}</span></span>\n </div>\n <button type=\"button\" class=\"open\" (click)=\"toggleRail(true)\">\n {{ 'order_builder.summary' | translate }}\n </button>\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block}:host .order-builder{display:grid;gap:var(--s-3)}:host .order-builder .head-actions{display:flex;align-items:center;gap:6px}:host .order-builder .ob-empty{padding:28px 16px;text-align:center;color:var(--text-mute);font-size:13px}:host .order-builder .text-mute{color:var(--text-mute);font-weight:600}:host .order-builder .form-grid{display:grid;gap:var(--s-3);grid-template-columns:repeat(12,minmax(0,1fr))}:host .order-builder .form-grid>.col-3{grid-column:span 3}:host .order-builder .form-grid>.col-4{grid-column:span 4}:host .order-builder .form-grid>.col-5{grid-column:span 5}:host .order-builder .form-grid>.col-6{grid-column:span 6}@media(max-width:767px){:host .order-builder .form-grid>[class^=col-]{grid-column:span 12}}:host .order-builder .field{display:grid;gap:4px}:host .order-builder .field>label{font-size:11px;font-weight:700;color:var(--text-mute);text-transform:uppercase;letter-spacing:.06em}:host .order-builder .field .req{color:var(--st-cancelled-fg);margin-inline-start:2px}:host .order-builder .ob-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--s-3);align-items:start}@media(min-width:1280px){:host .order-builder .ob-grid{grid-template-columns:minmax(0,2fr) minmax(0,1fr)}}:host .order-builder .ob-main{display:grid;gap:var(--s-3);min-width:0}:host .order-builder .ob-rail{display:grid;gap:var(--s-3);align-content:start}@media(min-width:1280px){:host .order-builder .ob-rail{position:sticky;top:44px}}:host .order-builder .ob-order-head{display:grid;gap:var(--s-4)}:host .order-builder .qa-grid{display:flex;flex-wrap:wrap;gap:10px;align-items:end;padding-top:var(--s-3);border-top:1px solid var(--rule-soft)}:host .order-builder .qa-combo-wrap{flex:1 1 260px;min-width:0}:host .order-builder .qa-price{flex:0 1 132px}:host .order-builder .qa-qtyf{flex:0 0 132px}:host .order-builder .qa-go{flex:0 0 auto}@media(max-width:560px){:host .order-builder .qa-combo-wrap,:host .order-builder .qa-price,:host .order-builder .qa-qtyf,:host .order-builder .qa-go{flex:1 1 100%}}:host .order-builder .qin{width:58px;text-align:end;font:inherit;font-family:var(--font-mono);font-weight:700;padding:6px 8px;min-height:34px;border:1px solid var(--rule);border-radius:var(--r-sm);background:var(--paper-alt);color:var(--text);outline:0;-moz-appearance:textfield}:host .order-builder .qin::-webkit-outer-spin-button,:host .order-builder .qin::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}:host .order-builder .qin:focus{border-color:var(--tenant-400);box-shadow:0 0 0 3px var(--tenant-100)}:host .order-builder .ob-tbl-scroll{min-width:0;max-width:100%;overflow-x:auto;overscroll-behavior-x:contain}:host .order-builder .ob-tbl{margin-inline:-16px;border-collapse:separate;border-spacing:0;font-size:13px}:host .order-builder .ob-tbl thead th:first-child,:host .order-builder .ob-tbl tbody td:first-child{padding-inline-start:16px}:host .order-builder .ob-tbl thead th:last-child,:host .order-builder .ob-tbl tbody td:last-child{padding-inline-end:16px}:host .order-builder .ob-tbl thead th{position:sticky;top:0;z-index:2;background:var(--paper);text-align:end;padding:8px 10px;font-size:10px;text-transform:uppercase;letter-spacing:.06em;font-weight:800;color:var(--text-mute);white-space:nowrap;box-shadow:inset 0 -1px 0 var(--rule)}:host .order-builder .ob-tbl thead th.col-prod{text-align:start}:host .order-builder .ob-tbl thead th.col-act{width:42px}:host .order-builder .ob-tbl tbody td{padding:7px 10px;text-align:end;border-bottom:1px solid var(--rule-soft);vertical-align:middle;white-space:nowrap}:host .order-builder .ob-tbl tbody tr:hover td{background:var(--paper)}:host .order-builder .ob-tbl .col-prod{text-align:start;min-width:186px}@media(max-width:767px){:host .order-builder .ob-tbl .col-prod{min-width:168px}}:host .order-builder .ob-tbl .p{display:flex;align-items:baseline;gap:9px;line-height:1.25}:host .order-builder .ob-tbl .p .ln{flex:0 0 auto;min-width:15px;text-align:end;font-family:var(--font-mono);font-size:10.5px;font-weight:700;color:var(--text-soft)}:host .order-builder .ob-tbl .p .pp{display:grid;gap:1px;min-width:0}:host .order-builder .ob-tbl .p .nm{font-weight:600;letter-spacing:-.005em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .order-builder .ob-tbl .p .nm em{font-style:normal;color:var(--text-mute);font-weight:600}:host .order-builder .ob-tbl .p .ref{font-family:var(--font-mono);font-size:10.5px;color:var(--text-soft);font-weight:600}:host .order-builder .ob-tbl .num,:host .order-builder .ob-tbl .total{font-family:var(--font-mono);font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums}:host .order-builder .ob-tbl .ln-val{appearance:none;width:100%;margin:0;padding:5px 8px;border:1px solid transparent;border-radius:7px;background:transparent;font:inherit;font-family:var(--font-mono);font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums;color:inherit;text-align:end;cursor:text;transition:background .12s ease,border-color .12s ease}:host .order-builder .ob-tbl .ln-val:hover:not(:disabled){background:var(--paper);border-color:var(--rule)}:host .order-builder .ob-tbl .ln-val:focus-visible{outline:none;background:var(--paper);border-color:var(--tenant, var(--rule))}:host .order-builder .ob-tbl .ln-val:disabled{cursor:default;background:transparent;border-color:transparent}:host .order-builder .ob-tbl .total{font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:13.5px}:host .order-builder .ob-tbl .cur{font-size:9.5px;color:var(--text-soft);margin-inline-start:3px}:host .order-builder .ob-tbl .ed{cursor:text;padding:4px 7px;border-radius:6px;border:1px solid transparent;display:inline-block;font-family:var(--font-mono)}:host .order-builder .ob-tbl .ed:hover{background:var(--paper-alt);border-color:var(--rule)}:host .order-builder .ob-tbl .free{color:var(--st-delivered-fg)}:host .order-builder .ob-del{color:var(--text-soft)}:host .order-builder .ob-del:hover{color:var(--st-cancelled-fg);border-color:var(--st-cancelled-bg)}:host .order-builder .ob-totals{display:grid;gap:6px}:host .order-builder .ob-totals .row{display:flex;justify-content:space-between;align-items:baseline;gap:14px}:host .order-builder .ob-totals .row .lbl{font-size:12px;font-weight:600;color:var(--text-mute)}:host .order-builder .ob-totals .row .v{font-family:var(--font-mono);font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums;font-weight:700;font-size:12.5px}:host .order-builder .ob-totals .row.discount .v{color:var(--st-pending-fg)}:host .order-builder .ob-totals hr{border:0;border-top:1px solid var(--rule);margin:5px 0}:host .order-builder .ob-totals .grand .lbl{font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:13.5px;color:var(--text)}:host .order-builder .ob-totals .grand .v{font-family:var(--font-display);font-variation-settings:\"opsz\" 96,\"wght\" 700,\"wdth\" 80;font-size:26px;letter-spacing:-.03em;color:var(--text)}:host .order-builder .ob-totals .grand .cur{font-family:var(--font-mono);font-size:.4em;color:var(--text-mute);margin-inline-start:4px}:host .order-builder .ob-recap .cols{display:grid;grid-template-columns:1fr 1fr}:host .order-builder .ob-recap .grp{display:flex;flex-direction:column;gap:3px;min-width:0}:host .order-builder .ob-recap .grp:first-child{padding-inline-end:14px}:host .order-builder .ob-recap .grp+.grp{padding-inline-start:14px;border-inline-start:1px solid var(--rule)}:host .order-builder .ob-recap .seg{display:flex;justify-content:space-between;align-items:baseline;gap:8px}:host .order-builder .ob-recap .seg .fmt{font-size:11.5px;font-weight:700;color:var(--text);letter-spacing:-.005em;min-width:0}:host .order-builder .ob-recap .seg .fmt:after{content:\":\";color:var(--text-soft)}:host .order-builder .ob-recap .seg .n{font-family:var(--font-mono);font-weight:700;font-size:12px;color:var(--text);font-variant-numeric:tabular-nums;flex-shrink:0}:host .order-builder .ob-recap .tot{display:flex;justify-content:space-between;align-items:baseline;margin-top:12px;padding-top:10px;border-top:1px solid var(--rule)}:host .order-builder .ob-recap .tot .l{font-weight:700;font-size:12px}:host .order-builder .ob-recap .tot .n{font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:18px;font-variant-numeric:tabular-nums}:host .order-builder .ob-rail-close{display:none}:host .order-builder .ob-fab{display:none;position:fixed;inset-inline-end:20px;bottom:20px;z-index:30;align-items:center;gap:10px;background:var(--ink-active);color:var(--paper);border-radius:var(--r-pill);padding:8px 10px 8px 16px;box-shadow:var(--shadow-3)}:host .order-builder .ob-fab .ft{display:grid;line-height:1.1}:host .order-builder .ob-fab .ft .k{font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;opacity:.65}:host .order-builder .ob-fab .ft .vv{font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 82;font-size:16px}:host .order-builder .ob-fab .ft .vv .cur{font-family:var(--font-mono);font-size:.6em;opacity:.7;margin-inline-start:3px}:host .order-builder .ob-fab .open{appearance:none;cursor:pointer;border:0;background:#ffffff24;color:#fff;font:inherit;font-size:12px;font-weight:700;border-radius:var(--r-pill);padding:8px 14px;display:inline-flex;align-items:center;gap:6px}:host .order-builder .ob-fab .open:hover{background:#ffffff3d}:host .order-builder .ob-backdrop{display:none;position:fixed;inset:0;z-index:38;background:#0f11156b;-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px)}:host .order-builder.ob-collapsed .ob-grid{grid-template-columns:minmax(0,1fr)}:host .order-builder.ob-collapsed .ob-fab{display:flex}:host .order-builder.ob-collapsed .ob-rail{position:fixed;top:0;inset-inline-end:0;height:100dvh;width:min(360px,92vw);z-index:40;background:var(--paper);border-inline-start:1px solid var(--rule);box-shadow:var(--shadow-3);padding:14px;overflow:auto;gap:var(--s-3);transform:translate(101%);transition:transform var(--t-base) var(--ease-out)}[dir=rtl] :host .order-builder.ob-collapsed .ob-rail{transform:translate(-101%)}:host .order-builder.ob-collapsed .ob-rail-close{display:inline-flex}:host .order-builder.ob-collapsed.ob-rail-open .ob-rail{transform:none}:host .order-builder.ob-collapsed.ob-rail-open .ob-backdrop{display:block}@media(max-width:1279px){:host .order-builder .ob-grid{grid-template-columns:minmax(0,1fr)}:host .order-builder .ob-fab{display:flex}:host .order-builder .ob-rail{position:fixed;top:0;inset-inline-end:0;height:100dvh;width:min(360px,92vw);z-index:40;background:var(--paper);border-inline-start:1px solid var(--rule);box-shadow:var(--shadow-3);padding:14px;overflow:auto;gap:var(--s-3);transform:translate(101%);transition:transform var(--t-base) var(--ease-out)}[dir=rtl] :host .order-builder .ob-rail{transform:translate(-101%)}:host .order-builder .ob-rail-close{display:inline-flex!important}:host .order-builder.ob-rail-open .ob-rail{transform:none}:host .order-builder.ob-rail-open .ob-backdrop{display:block}}:host .ob-tbl .nm,:host .ob-tbl .num,:host .ob-tbl .ln-val,:host .ob-totals .v,:host .ob-sum-val{unicode-bidi:plaintext}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "component", type: EfSelectComponent, selector: "ef-select", inputs: ["options", "optionLabel", "optionValue", "optionDisabled", "dataKey", "group", "optionGroupLabel", "optionGroupChildren", "size", "fluid", "inline", "appendTo", "multiple", "filter", "filterBy", "filterPlaceholder", "filterPlaceholderKey", "autofocusFilter", "editable", "loading", "virtualScroll", "virtualScrollItemSize", "scrollHeight", "multiDisplay", "showToggleAll", "emptyMessage", "emptyMessageKey", "emptyFilterMessage", "emptyFilterMessageKey", "styleClass", "panelStyleClass", "filterByKeys", "variant", "value"], outputs: ["selectionChangeEvent", "filterChangeEvent", "panelShowEvent", "panelHideEvent"] }, { kind: "component", type: EfDatepickerAdvancedComponent, selector: "ef-datepicker-advanced", inputs: ["triggerLabelKey", "triggerLabel", "presets", "activePreset", "start", "end", "allowCustomRange", "mode", "selectionMode", "variant", "showTime"], outputs: ["rangeChange", "opened", "closed"] }, { kind: "component", type: EfInputNumberComponent, selector: "ef-inputnumber", inputs: ["value", "invalid", "format", "showButtons", "buttonLayout", "incrementButtonClass", "decrementButtonClass", "incrementButtonIcon", "decrementButtonIcon", "prefix", "suffix", "currency", "currencyDisplay", "locale", "localeMatcher", "mode", "useGrouping", "minFractionDigits", "maxFractionDigits", "min", "max", "step", "allowEmpty", "inputSize", "size", "maxlength", "minlength", "pattern", "tabindex", "title", "ariaLabelledBy", "inputStyle", "inputStyleClass", "style", "styleClass", "primeNgVariant", "autofocus", "autocomplete", "fluid", "inline", "variant", "pt", "ptOptions", "dt", "unstyled"], outputs: ["inputEvent", "valueChangeEvent", "focusEvent", "blurEvent", "keyDownEvent", "clearEvent"] }, { kind: "component", type: EfLabelComponent, selector: "ef-label", inputs: ["label", "labelKey", "for", "styleClass", "required"] }, { kind: "component", type: EfButtonComponent, selector: "ef-button", inputs: ["label", "labelKey", "icon", "variant", "severity", "size", "disabled", "rounded", "outlined", "raised", "text", "loading", "styleClass", "type", "badge", "badgeSeverity", "tooltip", "tooltipKey", "tooltipPosition"], outputs: ["clickEvent"] }, { kind: "component", type: EfCardComponent, selector: "ef-card", inputs: ["titleKey", "title", "metaKey", "meta", "tone", "forceHead", "hasFoot", "collapsible", "overflowVisible", "loading", "skeleton", "skeletonRows", "skeletonHeight", "collapsed"], outputs: ["collapsedChange"] }, { kind: "component", type: EfChangeHistoryComponent, selector: "ef-change-history", inputs: ["entries", "titleKey", "emptyKey", "limit", "previewLimit", "collapsible", "startCollapsed"] }, { kind: "component", type: EfProductTypeaheadComponent, selector: "ef-product-typeahead", inputs: ["products", "productKeyField", "placeholderKey", "disabled", "currency", "locale", "showVariants"], outputs: ["productSelect"] }, { kind: "pipe", type: i1$3.DecimalPipe, name: "number" }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }] });
|
|
4092
4117
|
}
|
|
4093
4118
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfOrderBuilderComponent, decorators: [{
|
|
4094
4119
|
type: Component,
|
|
@@ -4105,7 +4130,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImpo
|
|
|
4105
4130
|
EfCardComponent,
|
|
4106
4131
|
EfChangeHistoryComponent,
|
|
4107
4132
|
EfProductTypeaheadComponent,
|
|
4108
|
-
], template: "<div\n class=\"order-builder\"\n [class.ob-collapsed]=\"focusMode()\"\n [class.ob-rail-open]=\"railOpen()\">\n <div class=\"ob-grid\">\n <div class=\"ob-main\">\n <!-- Order header: customer + date + express add (one card) -->\n <ef-card\n titleKey=\"order_builder.section_order\"\n [metaKey]=\"readonly() ? '' : 'order_builder.quick_add_hint'\"\n [overflowVisible]=\"true\">\n <div class=\"ob-order-head\">\n <div class=\"form-grid\">\n <div class=\"field col-6\">\n <ef-label labelKey=\"label.customer\" for=\"ob-cust\" [required]=\"true\" />\n <ef-select\n inputId=\"ob-cust\"\n [options]=\"customers()\"\n optionLabel=\"companyName\"\n optionValue=\"customerId\"\n [ngModel]=\"order().customerId\"\n (ngModelChange)=\"updateCustomer($event)\"\n [disabled]=\"readonly()\"\n [showClear]=\"true\"\n [filter]=\"true\"\n filterBy=\"companyName\"\n filterPlaceholderKey=\"label.search_customer\"\n appendTo=\"body\"\n placeholderKey=\"label.select_customer\" />\n </div>\n <div class=\"field col-3\">\n <ef-label labelKey=\"label.order_date\" for=\"ob-date\" [required]=\"true\" />\n <ef-datepicker-advanced\n mode=\"field\"\n selectionMode=\"single\"\n inputId=\"ob-date\"\n placeholderKey=\"date_select_date\"\n [ngModel]=\"order().orderDate\"\n (ngModelChange)=\"updateOrderDate($event)\"\n [disabled]=\"readonly()\" />\n </div>\n </div>\n\n @if (!readonly()) {\n <div class=\"qa-grid\">\n <div class=\"qa-combo-wrap\">\n <ef-label labelKey=\"label.article\" />\n <ef-product-typeahead\n [products]=\"products()\"\n [productKeyField]=\"productKeyField()\"\n placeholderKey=\"label.search_product\"\n [currency]=\"currency()\"\n [locale]=\"locale()\"\n (productSelect)=\"onTypeaheadSelect($event)\"\n [showVariants]=\"false\" />\n </div>\n <div class=\"qa-price\">\n <ef-label labelKey=\"label.sale_price\" />\n <ef-inputnumber\n variant=\"comptoir\"\n [value]=\"quickAddPrice()\"\n (valueChangeEvent)=\"quickAddPrice.set($event ?? 0)\"\n [minFractionDigits]=\"2\"\n [maxFractionDigits]=\"2\"\n [locale]=\"locale()\"\n [disabled]=\"readonly()\" />\n </div>\n <div class=\"qa-qtyf\">\n <ef-label labelKey=\"label.quantity\" />\n <ef-inputnumber\n variant=\"comptoir\"\n [ngModel]=\"quickAddQuantity()\"\n (ngModelChange)=\"quickAddQuantity.set($event)\"\n [min]=\"0\"\n [maxFractionDigits]=\"4\"\n [locale]=\"locale()\"\n [disabled]=\"readonly()\"\n />\n </div>\n <div class=\"qa-go\">\n <ef-button\n labelKey=\"action.add\"\n icon=\"pi pi-plus\"\n variant=\"comptoir\"\n severity=\"tenant\"\n [disabled]=\"quickAddDisabled()\"\n (clickEvent)=\"addQuickProduct()\" />\n </div>\n </div>\n }\n </div>\n </ef-card>\n\n <!-- Lines -->\n <ef-card titleKey=\"order_builder.lines\" [meta]=\"'\u00B7 ' + orderLines().length\">\n <div head-extra class=\"head-actions\">\n @if (config().showCatalogue && !readonly()) {\n <ef-button\n labelKey=\"action.open_catalogue\"\n icon=\"pi pi-box\"\n severity=\"secondary\"\n [text]=\"true\"\n (clickEvent)=\"handleOpenCatalogue()\" />\n }\n <ef-button\n [labelKey]=\"focusMode() ? 'order_builder.show_summary' : 'order_builder.fullscreen'\"\n icon=\"pi pi-window-maximize\"\n severity=\"secondary\"\n [text]=\"true\"\n (clickEvent)=\"toggleFocus()\" />\n </div>\n\n <!-- The lines table carries seven columns of numbers and cannot\n usefully shrink to 390px, so it scrolls inside its own box\n rather than widening the page. It had no scroller at all: at\n 390px the table measured 540px and was simply clipped. -->\n <div class=\"ob-tbl-scroll\">\n <table class=\"ob-tbl\">\n <thead>\n <tr>\n <th class=\"col-prod\">{{ 'label.article' | translate }}</th>\n <th>{{ 'label.sale_price' | translate }}</th>\n <th>{{ 'label.quantity' | translate }}</th>\n @if (config().enableDiscount) {\n <th>{{ 'label.discount' | translate }}</th>\n }\n @if (config().enableTax) {\n <th>{{ 'order_builder.tax' | translate }}</th>\n }\n <th>{{ 'label.subtotal' | translate }}</th>\n <th class=\"col-act\"></th>\n </tr>\n </thead>\n <tbody>\n @for (item of orderLines(); track item.id; let i = $index) {\n <tr>\n <td class=\"col-prod\">\n <div class=\"p\">\n <span class=\"ln\">{{ i + 1 }}</span>\n <span class=\"pp\">\n <span class=\"nm\">{{ item.productDescription }}</span>\n </span>\n </div>\n </td>\n <td class=\"num w-[120px]\">\n @if (!readonly() && isEditingCell(item, 'price')) {\n <ef-inputnumber\n [(ngModel)]=\"item.productUnitPrice\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [minFractionDigits]=\"2\"\n [maxFractionDigits]=\"2\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button\n type=\"button\"\n class=\"ln-val\"\n [disabled]=\"readonly()\"\n (click)=\"startEditCell(item, 'price')\">\n {{ item.productUnitPrice | number: '1.2-2' : locale() }}\n </button>\n }\n </td>\n <td class=\"num w-[120px]\">\n @if (!readonly() && isEditingCell(item, 'qty')) {\n <ef-inputnumber\n [(ngModel)]=\"item.quantity\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [maxFractionDigits]=\"4\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button\n type=\"button\"\n class=\"ln-val\"\n [disabled]=\"readonly()\"\n (click)=\"startEditCell(item, 'qty')\">\n {{ item.quantity | number: '1.0-4' : locale() }}\n </button>\n }\n </td>\n @if (config().enableDiscount) {\n <td class=\"num w-[80px]\">\n @if (!readonly() && isEditingCell(item, 'discount')) {\n <ef-inputnumber\n [(ngModel)]=\"item.discount\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [max]=\"100\"\n [maxFractionDigits]=\"2\"\n suffix=\" %\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button\n type=\"button\"\n class=\"ln-val\"\n [disabled]=\"readonly()\"\n (click)=\"startEditCell(item, 'discount')\">\n {{ item.discount | number: '1.0-2' : locale() }} %\n </button>\n }\n </td>\n }\n @if (config().enableTax) {\n <td class=\"num w-[80px]\">\n @if (!readonly() && isEditingCell(item, 'tva')) {\n <ef-inputnumber\n [(ngModel)]=\"item.taxRate\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [max]=\"100\"\n [maxFractionDigits]=\"2\"\n suffix=\" %\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button\n type=\"button\"\n class=\"ln-val\"\n [disabled]=\"readonly()\"\n (click)=\"startEditCell(item, 'tva')\">\n {{ (item.taxRate ?? 0) | number: '1.0-2' : locale() }} %\n </button>\n }\n </td>\n }\n <td class=\"total\">\n {{ calculateSubtotal(item) | number: '1.2-2' : locale() }}\n </td>\n <td class=\"col-act\">\n @if (!readonly()) {\n <ef-button\n icon=\"pi pi-trash\"\n severity=\"danger\"\n [text]=\"true\"\n styleClass=\"ob-del\"\n (clickEvent)=\"removeOrderLine(item.id)\" />\n }\n </td>\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"colCount()\">\n <div class=\"ob-empty\">{{ 'order_builder.empty' | translate }}</div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </ef-card>\n </div>\n\n <!-- CONTEXT RAIL (1/3) -->\n <aside class=\"ob-rail\">\n <!-- Totaux (top) -->\n <ef-card titleKey=\"order_builder.totals\" [meta]=\"currency()\">\n <div class=\"ob-totals\">\n <div class=\"row\">\n <span class=\"lbl\">{{ 'order_builder.gross' | translate }}</span>\n <span class=\"v\">{{ grossTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n <div class=\"row discount\">\n <span class=\"lbl\">{{ 'order_builder.discount' | translate }}</span>\n <span class=\"v\">\u2212{{ discountTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n <div class=\"row\">\n <span class=\"lbl\">{{ 'order_builder.tax' | translate }}</span>\n <span class=\"v\">{{ tvaTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n <hr />\n <div class=\"row grand\">\n <span class=\"lbl\">{{ 'order_builder.grand_total' | translate }}</span>\n <span class=\"v\">{{ finalTotal() | number: '1.2-2' : locale() }}<span class=\"cur\">{{ currency() }}</span></span>\n </div>\n </div>\n </ef-card>\n\n <!-- R\u00E9capitulatif par format -->\n @if (productsSummary().items.length > 0) {\n <ef-card titleKey=\"order_builder.recap\" metaKey=\"order_builder.auto\">\n <div class=\"ob-recap\">\n <div class=\"cols\">\n @for (col of recapColumns(); track $index) {\n <div class=\"grp\">\n @for (seg of col; track seg.code) {\n <div class=\"seg\">\n <span class=\"fmt\">{{ seg.label }}</span>\n <span class=\"n\">{{ seg.count | number: '1.0-0' : locale() }}</span>\n </div>\n }\n </div>\n }\n </div>\n <div class=\"tot\">\n <span class=\"l\">{{ 'order_builder.total_units' | translate }}</span>\n <span class=\"n\">{{ productsSummary().totalCount | number: '1.0-0' : locale() }}</span>\n </div>\n </div>\n </ef-card>\n }\n\n <!-- Historique (optional, component-owned) -->\n @if (hasAudit()) {\n <ef-change-history [entries]=\"auditEntries()\" />\n }\n </aside>\n </div>\n\n <!-- Floating summary FAB + backdrop -->\n <button type=\"button\" class=\"ob-backdrop\" aria-hidden=\"true\" tabindex=\"-1\" (click)=\"toggleRail(false)\"></button>\n <div class=\"ob-fab\" role=\"status\">\n <div class=\"ft\">\n <span class=\"k\">{{ 'order_builder.total_units' | translate }} \u00B7 {{ productsSummary().totalCount }}</span>\n <span class=\"vv\">{{ finalTotal() | number: '1.2-2' : locale() }}<span class=\"cur\">{{ currency() }}</span></span>\n </div>\n <button type=\"button\" class=\"open\" (click)=\"toggleRail(true)\">\n {{ 'order_builder.summary' | translate }}\n </button>\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block}:host .order-builder{display:grid;gap:var(--s-3)}:host .order-builder .head-actions{display:flex;align-items:center;gap:6px}:host .order-builder .ob-empty{padding:28px 16px;text-align:center;color:var(--text-mute);font-size:13px}:host .order-builder .text-mute{color:var(--text-mute);font-weight:600}:host .order-builder .form-grid{display:grid;gap:var(--s-3);grid-template-columns:repeat(12,minmax(0,1fr))}:host .order-builder .form-grid>.col-3{grid-column:span 3}:host .order-builder .form-grid>.col-4{grid-column:span 4}:host .order-builder .form-grid>.col-5{grid-column:span 5}:host .order-builder .form-grid>.col-6{grid-column:span 6}@media(max-width:767px){:host .order-builder .form-grid>[class^=col-]{grid-column:span 12}}:host .order-builder .field{display:grid;gap:4px}:host .order-builder .field>label{font-size:11px;font-weight:700;color:var(--text-mute);text-transform:uppercase;letter-spacing:.06em}:host .order-builder .field .req{color:var(--st-cancelled-fg);margin-inline-start:2px}:host .order-builder .ob-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--s-3);align-items:start}@media(min-width:1280px){:host .order-builder .ob-grid{grid-template-columns:minmax(0,2fr) minmax(0,1fr)}}:host .order-builder .ob-main{display:grid;gap:var(--s-3);min-width:0}:host .order-builder .ob-rail{display:grid;gap:var(--s-3);align-content:start}@media(min-width:1280px){:host .order-builder .ob-rail{position:sticky;top:44px}}:host .order-builder .ob-order-head{display:grid;gap:var(--s-4)}:host .order-builder .qa-grid{display:flex;flex-wrap:wrap;gap:10px;align-items:end;padding-top:var(--s-3);border-top:1px solid var(--rule-soft)}:host .order-builder .qa-combo-wrap{flex:1 1 260px;min-width:0}:host .order-builder .qa-price{flex:0 1 132px}:host .order-builder .qa-qtyf{flex:0 0 132px}:host .order-builder .qa-go{flex:0 0 auto}@media(max-width:560px){:host .order-builder .qa-combo-wrap,:host .order-builder .qa-price,:host .order-builder .qa-qtyf,:host .order-builder .qa-go{flex:1 1 100%}}:host .order-builder .qin{width:58px;text-align:end;font:inherit;font-family:var(--font-mono);font-weight:700;padding:6px 8px;min-height:34px;border:1px solid var(--rule);border-radius:var(--r-sm);background:var(--paper-alt);color:var(--text);outline:0;-moz-appearance:textfield}:host .order-builder .qin::-webkit-outer-spin-button,:host .order-builder .qin::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}:host .order-builder .qin:focus{border-color:var(--tenant-400);box-shadow:0 0 0 3px var(--tenant-100)}:host .order-builder .ob-tbl-scroll{min-width:0;max-width:100%;overflow-x:auto;overscroll-behavior-x:contain}:host .order-builder .ob-tbl{margin-inline:-16px;border-collapse:separate;border-spacing:0;font-size:13px}:host .order-builder .ob-tbl thead th:first-child,:host .order-builder .ob-tbl tbody td:first-child{padding-inline-start:16px}:host .order-builder .ob-tbl thead th:last-child,:host .order-builder .ob-tbl tbody td:last-child{padding-inline-end:16px}:host .order-builder .ob-tbl thead th{position:sticky;top:0;z-index:2;background:var(--paper);text-align:end;padding:8px 10px;font-size:10px;text-transform:uppercase;letter-spacing:.06em;font-weight:800;color:var(--text-mute);white-space:nowrap;box-shadow:inset 0 -1px 0 var(--rule)}:host .order-builder .ob-tbl thead th.col-prod{text-align:start}:host .order-builder .ob-tbl thead th.col-act{width:42px}:host .order-builder .ob-tbl tbody td{padding:7px 10px;text-align:end;border-bottom:1px solid var(--rule-soft);vertical-align:middle;white-space:nowrap}:host .order-builder .ob-tbl tbody tr:hover td{background:var(--paper)}:host .order-builder .ob-tbl .col-prod{text-align:start;min-width:186px}@media(max-width:767px){:host .order-builder .ob-tbl .col-prod{min-width:168px}}:host .order-builder .ob-tbl .p{display:flex;align-items:baseline;gap:9px;line-height:1.25}:host .order-builder .ob-tbl .p .ln{flex:0 0 auto;min-width:15px;text-align:end;font-family:var(--font-mono);font-size:10.5px;font-weight:700;color:var(--text-soft)}:host .order-builder .ob-tbl .p .pp{display:grid;gap:1px;min-width:0}:host .order-builder .ob-tbl .p .nm{font-weight:600;letter-spacing:-.005em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .order-builder .ob-tbl .p .nm em{font-style:normal;color:var(--text-mute);font-weight:600}:host .order-builder .ob-tbl .p .ref{font-family:var(--font-mono);font-size:10.5px;color:var(--text-soft);font-weight:600}:host .order-builder .ob-tbl .num,:host .order-builder .ob-tbl .total{font-family:var(--font-mono);font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums}:host .order-builder .ob-tbl .ln-val{appearance:none;width:100%;margin:0;padding:5px 8px;border:1px solid transparent;border-radius:7px;background:transparent;font:inherit;font-family:var(--font-mono);font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums;color:inherit;text-align:end;cursor:text;transition:background .12s ease,border-color .12s ease}:host .order-builder .ob-tbl .ln-val:hover:not(:disabled){background:var(--paper);border-color:var(--rule)}:host .order-builder .ob-tbl .ln-val:focus-visible{outline:none;background:var(--paper);border-color:var(--tenant, var(--rule))}:host .order-builder .ob-tbl .ln-val:disabled{cursor:default;background:transparent;border-color:transparent}:host .order-builder .ob-tbl .total{font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:13.5px}:host .order-builder .ob-tbl .cur{font-size:9.5px;color:var(--text-soft);margin-inline-start:3px}:host .order-builder .ob-tbl .ed{cursor:text;padding:4px 7px;border-radius:6px;border:1px solid transparent;display:inline-block;font-family:var(--font-mono)}:host .order-builder .ob-tbl .ed:hover{background:var(--paper-alt);border-color:var(--rule)}:host .order-builder .ob-tbl .free{color:var(--st-delivered-fg)}:host .order-builder .ob-del{color:var(--text-soft)}:host .order-builder .ob-del:hover{color:var(--st-cancelled-fg);border-color:var(--st-cancelled-bg)}:host .order-builder .ob-totals{display:grid;gap:6px}:host .order-builder .ob-totals .row{display:flex;justify-content:space-between;align-items:baseline;gap:14px}:host .order-builder .ob-totals .row .lbl{font-size:12px;font-weight:600;color:var(--text-mute)}:host .order-builder .ob-totals .row .v{font-family:var(--font-mono);font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums;font-weight:700;font-size:12.5px}:host .order-builder .ob-totals .row.discount .v{color:var(--st-pending-fg)}:host .order-builder .ob-totals hr{border:0;border-top:1px solid var(--rule);margin:5px 0}:host .order-builder .ob-totals .grand .lbl{font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:13.5px;color:var(--text)}:host .order-builder .ob-totals .grand .v{font-family:var(--font-display);font-variation-settings:\"opsz\" 96,\"wght\" 700,\"wdth\" 80;font-size:26px;letter-spacing:-.03em;color:var(--text)}:host .order-builder .ob-totals .grand .cur{font-family:var(--font-mono);font-size:.4em;color:var(--text-mute);margin-inline-start:4px}:host .order-builder .ob-recap .cols{display:grid;grid-template-columns:1fr 1fr}:host .order-builder .ob-recap .grp{display:flex;flex-direction:column;gap:3px;min-width:0}:host .order-builder .ob-recap .grp:first-child{padding-inline-end:14px}:host .order-builder .ob-recap .grp+.grp{padding-inline-start:14px;border-inline-start:1px solid var(--rule)}:host .order-builder .ob-recap .seg{display:flex;justify-content:space-between;align-items:baseline;gap:8px}:host .order-builder .ob-recap .seg .fmt{font-size:11.5px;font-weight:700;color:var(--text);letter-spacing:-.005em;min-width:0}:host .order-builder .ob-recap .seg .fmt:after{content:\":\";color:var(--text-soft)}:host .order-builder .ob-recap .seg .n{font-family:var(--font-mono);font-weight:700;font-size:12px;color:var(--text);font-variant-numeric:tabular-nums;flex-shrink:0}:host .order-builder .ob-recap .tot{display:flex;justify-content:space-between;align-items:baseline;margin-top:12px;padding-top:10px;border-top:1px solid var(--rule)}:host .order-builder .ob-recap .tot .l{font-weight:700;font-size:12px}:host .order-builder .ob-recap .tot .n{font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:18px;font-variant-numeric:tabular-nums}:host .order-builder .ob-rail-close{display:none}:host .order-builder .ob-fab{display:none;position:fixed;inset-inline-end:20px;bottom:20px;z-index:30;align-items:center;gap:10px;background:var(--ink-active);color:var(--paper);border-radius:var(--r-pill);padding:8px 10px 8px 16px;box-shadow:var(--shadow-3)}:host .order-builder .ob-fab .ft{display:grid;line-height:1.1}:host .order-builder .ob-fab .ft .k{font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;opacity:.65}:host .order-builder .ob-fab .ft .vv{font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 82;font-size:16px}:host .order-builder .ob-fab .ft .vv .cur{font-family:var(--font-mono);font-size:.6em;opacity:.7;margin-inline-start:3px}:host .order-builder .ob-fab .open{appearance:none;cursor:pointer;border:0;background:#ffffff24;color:#fff;font:inherit;font-size:12px;font-weight:700;border-radius:var(--r-pill);padding:8px 14px;display:inline-flex;align-items:center;gap:6px}:host .order-builder .ob-fab .open:hover{background:#ffffff3d}:host .order-builder .ob-backdrop{display:none;position:fixed;inset:0;z-index:38;background:#0f11156b;-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px)}:host .order-builder.ob-collapsed .ob-grid{grid-template-columns:minmax(0,1fr)}:host .order-builder.ob-collapsed .ob-fab{display:flex}:host .order-builder.ob-collapsed .ob-rail{position:fixed;top:0;inset-inline-end:0;height:100dvh;width:min(360px,92vw);z-index:40;background:var(--paper);border-inline-start:1px solid var(--rule);box-shadow:var(--shadow-3);padding:14px;overflow:auto;gap:var(--s-3);transform:translate(101%);transition:transform var(--t-base) var(--ease-out)}[dir=rtl] :host .order-builder.ob-collapsed .ob-rail{transform:translate(-101%)}:host .order-builder.ob-collapsed .ob-rail-close{display:inline-flex}:host .order-builder.ob-collapsed.ob-rail-open .ob-rail{transform:none}:host .order-builder.ob-collapsed.ob-rail-open .ob-backdrop{display:block}@media(max-width:1279px){:host .order-builder .ob-grid{grid-template-columns:minmax(0,1fr)}:host .order-builder .ob-fab{display:flex}:host .order-builder .ob-rail{position:fixed;top:0;inset-inline-end:0;height:100dvh;width:min(360px,92vw);z-index:40;background:var(--paper);border-inline-start:1px solid var(--rule);box-shadow:var(--shadow-3);padding:14px;overflow:auto;gap:var(--s-3);transform:translate(101%);transition:transform var(--t-base) var(--ease-out)}[dir=rtl] :host .order-builder .ob-rail{transform:translate(-101%)}:host .order-builder .ob-rail-close{display:inline-flex!important}:host .order-builder.ob-rail-open .ob-rail{transform:none}:host .order-builder.ob-rail-open .ob-backdrop{display:block}}\n"] }]
|
|
4133
|
+
], template: "<div\n class=\"order-builder\"\n [class.ob-collapsed]=\"focusMode()\"\n [class.ob-rail-open]=\"railOpen()\">\n <div class=\"ob-grid\">\n <div class=\"ob-main\">\n <!-- Order header: customer + date + express add (one card) -->\n <ef-card\n titleKey=\"order_builder.section_order\"\n [metaKey]=\"readonly() ? '' : 'order_builder.quick_add_hint'\"\n [overflowVisible]=\"true\">\n <div class=\"ob-order-head\">\n <div class=\"form-grid\">\n <div class=\"field col-6\">\n <ef-label labelKey=\"label.customer\" for=\"ob-cust\" [required]=\"true\" />\n <ef-select\n inputId=\"ob-cust\"\n [options]=\"customers()\"\n optionLabel=\"companyName\"\n optionValue=\"customerId\"\n [ngModel]=\"order().customerId\"\n (ngModelChange)=\"updateCustomer($event)\"\n [disabled]=\"readonly()\"\n [showClear]=\"true\"\n [filter]=\"true\"\n filterBy=\"companyName\"\n filterPlaceholderKey=\"label.search_customer\"\n appendTo=\"body\"\n placeholderKey=\"label.select_customer\" />\n </div>\n <div class=\"field col-3\">\n <ef-label labelKey=\"label.order_date\" for=\"ob-date\" [required]=\"true\" />\n <ef-datepicker-advanced\n mode=\"field\"\n selectionMode=\"single\"\n inputId=\"ob-date\"\n placeholderKey=\"date_select_date\"\n [ngModel]=\"order().orderDate\"\n (ngModelChange)=\"updateOrderDate($event)\"\n [disabled]=\"readonly()\" />\n </div>\n </div>\n\n @if (!readonly()) {\n <div class=\"qa-grid\">\n <div class=\"qa-combo-wrap\">\n <ef-label labelKey=\"label.article\" />\n <ef-product-typeahead\n [products]=\"products()\"\n [productKeyField]=\"productKeyField()\"\n placeholderKey=\"label.search_product\"\n [currency]=\"currency()\"\n [locale]=\"locale()\"\n (productSelect)=\"onTypeaheadSelect($event)\"\n [showVariants]=\"false\" />\n </div>\n <div class=\"qa-price\">\n <ef-label labelKey=\"label.sale_price\" />\n <ef-inputnumber\n variant=\"comptoir\"\n [value]=\"quickAddPrice()\"\n (valueChangeEvent)=\"quickAddPrice.set($event ?? 0)\"\n [minFractionDigits]=\"2\"\n [maxFractionDigits]=\"2\"\n [locale]=\"locale()\"\n [disabled]=\"readonly()\" />\n </div>\n <div class=\"qa-qtyf\">\n <ef-label labelKey=\"label.quantity\" />\n <ef-inputnumber\n variant=\"comptoir\"\n [ngModel]=\"quickAddQuantity()\"\n (ngModelChange)=\"quickAddQuantity.set($event)\"\n [min]=\"0\"\n [maxFractionDigits]=\"4\"\n [locale]=\"locale()\"\n [disabled]=\"readonly()\"\n />\n </div>\n <div class=\"qa-go\">\n <ef-button\n labelKey=\"action.add\"\n icon=\"pi pi-plus\"\n variant=\"comptoir\"\n severity=\"tenant\"\n [disabled]=\"quickAddDisabled()\"\n (clickEvent)=\"addQuickProduct()\" />\n </div>\n </div>\n }\n </div>\n </ef-card>\n\n <!-- Lines -->\n <ef-card titleKey=\"order_builder.lines\" [meta]=\"'\u00B7 ' + orderLines().length\">\n <div head-extra class=\"head-actions\">\n @if (config().showCatalogue && !readonly()) {\n <ef-button\n labelKey=\"action.open_catalogue\"\n icon=\"pi pi-box\"\n severity=\"secondary\"\n [text]=\"true\"\n (clickEvent)=\"handleOpenCatalogue()\" />\n }\n <ef-button\n [labelKey]=\"focusMode() ? 'order_builder.show_summary' : 'order_builder.fullscreen'\"\n icon=\"pi pi-window-maximize\"\n severity=\"secondary\"\n [text]=\"true\"\n (clickEvent)=\"toggleFocus()\" />\n </div>\n\n <!-- The lines table carries seven columns of numbers and cannot\n usefully shrink to 390px, so it scrolls inside its own box\n rather than widening the page. It had no scroller at all: at\n 390px the table measured 540px and was simply clipped. -->\n <div class=\"ob-tbl-scroll\">\n <table class=\"ob-tbl\">\n <thead>\n <tr>\n <th class=\"col-prod\">{{ 'label.article' | translate }}</th>\n <th>{{ 'label.sale_price' | translate }}</th>\n <th>{{ 'label.quantity' | translate }}</th>\n @if (config().enableDiscount) {\n <th>{{ 'label.discount' | translate }}</th>\n }\n @if (config().enableTax) {\n <th>{{ 'order_builder.tax' | translate }}</th>\n }\n <th>{{ 'label.subtotal' | translate }}</th>\n <th class=\"col-act\"></th>\n </tr>\n </thead>\n <tbody>\n @for (item of orderLines(); track item.id; let i = $index) {\n <tr>\n <td class=\"col-prod\">\n <div class=\"p\">\n <span class=\"ln\">{{ i + 1 }}</span>\n <span class=\"pp\">\n <span class=\"nm\">{{ item.productDescription }}</span>\n </span>\n </div>\n </td>\n <td class=\"num w-[120px]\">\n @if (!readonly() && isEditingCell(item, 'price')) {\n <ef-inputnumber\n [(ngModel)]=\"item.productUnitPrice\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [minFractionDigits]=\"2\"\n [maxFractionDigits]=\"2\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button\n type=\"button\"\n class=\"ln-val\"\n [disabled]=\"readonly()\"\n (click)=\"startEditCell(item, 'price')\">\n {{ item.productUnitPrice | number: '1.2-2' : locale() }}\n </button>\n }\n </td>\n <td class=\"num w-[120px]\">\n @if (!readonly() && isEditingCell(item, 'qty')) {\n <ef-inputnumber\n [(ngModel)]=\"item.quantity\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [maxFractionDigits]=\"4\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button\n type=\"button\"\n class=\"ln-val\"\n [disabled]=\"readonly()\"\n (click)=\"startEditCell(item, 'qty')\">\n {{ item.quantity | number: '1.0-4' : locale() }}\n </button>\n }\n </td>\n @if (config().enableDiscount) {\n <td class=\"num w-[80px]\">\n @if (!readonly() && isEditingCell(item, 'discount')) {\n <ef-inputnumber\n [(ngModel)]=\"item.discount\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [max]=\"100\"\n [maxFractionDigits]=\"2\"\n suffix=\" %\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button\n type=\"button\"\n class=\"ln-val\"\n [disabled]=\"readonly()\"\n (click)=\"startEditCell(item, 'discount')\">\n {{ item.discount | number: '1.0-2' : locale() }} %\n </button>\n }\n </td>\n }\n @if (config().enableTax) {\n <td class=\"num w-[80px]\">\n @if (!readonly() && isEditingCell(item, 'tva')) {\n <ef-inputnumber\n [(ngModel)]=\"item.taxRate\"\n (ngModelChange)=\"onFieldChange(item)\"\n [showClear]=\"false\"\n [autofocus]=\"true\"\n (blurEvent)=\"stopEditCell()\"\n (keyDownEvent)=\"onEditCellKeydown($event)\"\n [min]=\"0\"\n [max]=\"100\"\n [maxFractionDigits]=\"2\"\n suffix=\" %\"\n [locale]=\"locale()\"\n size=\"small\"\n class=\"!text-end\" />\n } @else {\n <button\n type=\"button\"\n class=\"ln-val\"\n [disabled]=\"readonly()\"\n (click)=\"startEditCell(item, 'tva')\">\n {{ (item.taxRate ?? 0) | number: '1.0-2' : locale() }} %\n </button>\n }\n </td>\n }\n <td class=\"total\">\n {{ calculateSubtotal(item) | number: '1.2-2' : locale() }}\n </td>\n <td class=\"col-act\">\n @if (!readonly()) {\n <ef-button\n icon=\"pi pi-trash\"\n severity=\"danger\"\n [text]=\"true\"\n styleClass=\"ob-del\"\n (clickEvent)=\"removeOrderLine(item.id)\" />\n }\n </td>\n </tr>\n } @empty {\n <tr>\n <td [attr.colspan]=\"colCount()\">\n <div class=\"ob-empty\">{{ 'order_builder.empty' | translate }}</div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </ef-card>\n </div>\n\n <!-- CONTEXT RAIL (1/3) -->\n <aside class=\"ob-rail\">\n <!-- Totaux (top) -->\n <ef-card titleKey=\"order_builder.totals\" [meta]=\"currency()\">\n <div class=\"ob-totals\">\n <div class=\"row\">\n <span class=\"lbl\">{{ 'order_builder.gross' | translate }}</span>\n <span class=\"v\">{{ grossTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n <div class=\"row discount\">\n <span class=\"lbl\">{{ 'order_builder.discount' | translate }}</span>\n <span class=\"v\">\u2212{{ discountTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n <div class=\"row\">\n <span class=\"lbl\">{{ 'order_builder.tax' | translate }}</span>\n <span class=\"v\">{{ tvaTotal() | number: '1.2-2' : locale() }}</span>\n </div>\n <hr />\n <div class=\"row grand\">\n <span class=\"lbl\">{{ 'order_builder.grand_total' | translate }}</span>\n <span class=\"v\">{{ finalTotal() | number: '1.2-2' : locale() }}<span class=\"cur\">{{ currency() }}</span></span>\n </div>\n </div>\n </ef-card>\n\n <!-- R\u00E9capitulatif par format -->\n @if (productsSummary().items.length > 0) {\n <ef-card titleKey=\"order_builder.recap\" metaKey=\"order_builder.auto\">\n <div class=\"ob-recap\">\n <div class=\"cols\">\n @for (col of recapColumns(); track $index) {\n <div class=\"grp\">\n @for (seg of col; track seg.code) {\n <div class=\"seg\">\n <span class=\"fmt\">{{ seg.label }}</span>\n <span class=\"n\">{{ seg.count | number: '1.0-0' : locale() }}</span>\n </div>\n }\n </div>\n }\n </div>\n <div class=\"tot\">\n <span class=\"l\">{{ 'order_builder.total_units' | translate }}</span>\n <span class=\"n\">{{ productsSummary().totalCount | number: '1.0-0' : locale() }}</span>\n </div>\n </div>\n </ef-card>\n }\n\n <!-- Historique (optional, component-owned) -->\n @if (hasAudit()) {\n <ef-change-history [entries]=\"auditEntries()\" />\n }\n </aside>\n </div>\n\n <!-- Floating summary FAB + backdrop -->\n <button type=\"button\" class=\"ob-backdrop\" aria-hidden=\"true\" tabindex=\"-1\" (click)=\"toggleRail(false)\"></button>\n <div class=\"ob-fab\" role=\"status\">\n <div class=\"ft\">\n <span class=\"k\">{{ 'order_builder.total_units' | translate }} \u00B7 {{ productsSummary().totalCount }}</span>\n <span class=\"vv\">{{ finalTotal() | number: '1.2-2' : locale() }}<span class=\"cur\">{{ currency() }}</span></span>\n </div>\n <button type=\"button\" class=\"open\" (click)=\"toggleRail(true)\">\n {{ 'order_builder.summary' | translate }}\n </button>\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block}:host .order-builder{display:grid;gap:var(--s-3)}:host .order-builder .head-actions{display:flex;align-items:center;gap:6px}:host .order-builder .ob-empty{padding:28px 16px;text-align:center;color:var(--text-mute);font-size:13px}:host .order-builder .text-mute{color:var(--text-mute);font-weight:600}:host .order-builder .form-grid{display:grid;gap:var(--s-3);grid-template-columns:repeat(12,minmax(0,1fr))}:host .order-builder .form-grid>.col-3{grid-column:span 3}:host .order-builder .form-grid>.col-4{grid-column:span 4}:host .order-builder .form-grid>.col-5{grid-column:span 5}:host .order-builder .form-grid>.col-6{grid-column:span 6}@media(max-width:767px){:host .order-builder .form-grid>[class^=col-]{grid-column:span 12}}:host .order-builder .field{display:grid;gap:4px}:host .order-builder .field>label{font-size:11px;font-weight:700;color:var(--text-mute);text-transform:uppercase;letter-spacing:.06em}:host .order-builder .field .req{color:var(--st-cancelled-fg);margin-inline-start:2px}:host .order-builder .ob-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--s-3);align-items:start}@media(min-width:1280px){:host .order-builder .ob-grid{grid-template-columns:minmax(0,2fr) minmax(0,1fr)}}:host .order-builder .ob-main{display:grid;gap:var(--s-3);min-width:0}:host .order-builder .ob-rail{display:grid;gap:var(--s-3);align-content:start}@media(min-width:1280px){:host .order-builder .ob-rail{position:sticky;top:44px}}:host .order-builder .ob-order-head{display:grid;gap:var(--s-4)}:host .order-builder .qa-grid{display:flex;flex-wrap:wrap;gap:10px;align-items:end;padding-top:var(--s-3);border-top:1px solid var(--rule-soft)}:host .order-builder .qa-combo-wrap{flex:1 1 260px;min-width:0}:host .order-builder .qa-price{flex:0 1 132px}:host .order-builder .qa-qtyf{flex:0 0 132px}:host .order-builder .qa-go{flex:0 0 auto}@media(max-width:560px){:host .order-builder .qa-combo-wrap,:host .order-builder .qa-price,:host .order-builder .qa-qtyf,:host .order-builder .qa-go{flex:1 1 100%}}:host .order-builder .qin{width:58px;text-align:end;font:inherit;font-family:var(--font-mono);font-weight:700;padding:6px 8px;min-height:34px;border:1px solid var(--rule);border-radius:var(--r-sm);background:var(--paper-alt);color:var(--text);outline:0;-moz-appearance:textfield}:host .order-builder .qin::-webkit-outer-spin-button,:host .order-builder .qin::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}:host .order-builder .qin:focus{border-color:var(--tenant-400);box-shadow:0 0 0 3px var(--tenant-100)}:host .order-builder .ob-tbl-scroll{min-width:0;max-width:100%;overflow-x:auto;overscroll-behavior-x:contain}:host .order-builder .ob-tbl{margin-inline:-16px;border-collapse:separate;border-spacing:0;font-size:13px}:host .order-builder .ob-tbl thead th:first-child,:host .order-builder .ob-tbl tbody td:first-child{padding-inline-start:16px}:host .order-builder .ob-tbl thead th:last-child,:host .order-builder .ob-tbl tbody td:last-child{padding-inline-end:16px}:host .order-builder .ob-tbl thead th{position:sticky;top:0;z-index:2;background:var(--paper);text-align:end;padding:8px 10px;font-size:10px;text-transform:uppercase;letter-spacing:.06em;font-weight:800;color:var(--text-mute);white-space:nowrap;box-shadow:inset 0 -1px 0 var(--rule)}:host .order-builder .ob-tbl thead th.col-prod{text-align:start}:host .order-builder .ob-tbl thead th.col-act{width:42px}:host .order-builder .ob-tbl tbody td{padding:7px 10px;text-align:end;border-bottom:1px solid var(--rule-soft);vertical-align:middle;white-space:nowrap}:host .order-builder .ob-tbl tbody tr:hover td{background:var(--paper)}:host .order-builder .ob-tbl .col-prod{text-align:start;min-width:186px}@media(max-width:767px){:host .order-builder .ob-tbl .col-prod{min-width:168px}}:host .order-builder .ob-tbl .p{display:flex;align-items:baseline;gap:9px;line-height:1.25}:host .order-builder .ob-tbl .p .ln{flex:0 0 auto;min-width:15px;text-align:end;font-family:var(--font-mono);font-size:10.5px;font-weight:700;color:var(--text-soft)}:host .order-builder .ob-tbl .p .pp{display:grid;gap:1px;min-width:0}:host .order-builder .ob-tbl .p .nm{font-weight:600;letter-spacing:-.005em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host .order-builder .ob-tbl .p .nm em{font-style:normal;color:var(--text-mute);font-weight:600}:host .order-builder .ob-tbl .p .ref{font-family:var(--font-mono);font-size:10.5px;color:var(--text-soft);font-weight:600}:host .order-builder .ob-tbl .num,:host .order-builder .ob-tbl .total{font-family:var(--font-mono);font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums}:host .order-builder .ob-tbl .ln-val{appearance:none;width:100%;margin:0;padding:5px 8px;border:1px solid transparent;border-radius:7px;background:transparent;font:inherit;font-family:var(--font-mono);font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums;color:inherit;text-align:end;cursor:text;transition:background .12s ease,border-color .12s ease}:host .order-builder .ob-tbl .ln-val:hover:not(:disabled){background:var(--paper);border-color:var(--rule)}:host .order-builder .ob-tbl .ln-val:focus-visible{outline:none;background:var(--paper);border-color:var(--tenant, var(--rule))}:host .order-builder .ob-tbl .ln-val:disabled{cursor:default;background:transparent;border-color:transparent}:host .order-builder .ob-tbl .total{font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:13.5px}:host .order-builder .ob-tbl .cur{font-size:9.5px;color:var(--text-soft);margin-inline-start:3px}:host .order-builder .ob-tbl .ed{cursor:text;padding:4px 7px;border-radius:6px;border:1px solid transparent;display:inline-block;font-family:var(--font-mono)}:host .order-builder .ob-tbl .ed:hover{background:var(--paper-alt);border-color:var(--rule)}:host .order-builder .ob-tbl .free{color:var(--st-delivered-fg)}:host .order-builder .ob-del{color:var(--text-soft)}:host .order-builder .ob-del:hover{color:var(--st-cancelled-fg);border-color:var(--st-cancelled-bg)}:host .order-builder .ob-totals{display:grid;gap:6px}:host .order-builder .ob-totals .row{display:flex;justify-content:space-between;align-items:baseline;gap:14px}:host .order-builder .ob-totals .row .lbl{font-size:12px;font-weight:600;color:var(--text-mute)}:host .order-builder .ob-totals .row .v{font-family:var(--font-mono);font-feature-settings:\"tnum\";font-variant-numeric:tabular-nums;font-weight:700;font-size:12.5px}:host .order-builder .ob-totals .row.discount .v{color:var(--st-pending-fg)}:host .order-builder .ob-totals hr{border:0;border-top:1px solid var(--rule);margin:5px 0}:host .order-builder .ob-totals .grand .lbl{font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:13.5px;color:var(--text)}:host .order-builder .ob-totals .grand .v{font-family:var(--font-display);font-variation-settings:\"opsz\" 96,\"wght\" 700,\"wdth\" 80;font-size:26px;letter-spacing:-.03em;color:var(--text)}:host .order-builder .ob-totals .grand .cur{font-family:var(--font-mono);font-size:.4em;color:var(--text-mute);margin-inline-start:4px}:host .order-builder .ob-recap .cols{display:grid;grid-template-columns:1fr 1fr}:host .order-builder .ob-recap .grp{display:flex;flex-direction:column;gap:3px;min-width:0}:host .order-builder .ob-recap .grp:first-child{padding-inline-end:14px}:host .order-builder .ob-recap .grp+.grp{padding-inline-start:14px;border-inline-start:1px solid var(--rule)}:host .order-builder .ob-recap .seg{display:flex;justify-content:space-between;align-items:baseline;gap:8px}:host .order-builder .ob-recap .seg .fmt{font-size:11.5px;font-weight:700;color:var(--text);letter-spacing:-.005em;min-width:0}:host .order-builder .ob-recap .seg .fmt:after{content:\":\";color:var(--text-soft)}:host .order-builder .ob-recap .seg .n{font-family:var(--font-mono);font-weight:700;font-size:12px;color:var(--text);font-variant-numeric:tabular-nums;flex-shrink:0}:host .order-builder .ob-recap .tot{display:flex;justify-content:space-between;align-items:baseline;margin-top:12px;padding-top:10px;border-top:1px solid var(--rule)}:host .order-builder .ob-recap .tot .l{font-weight:700;font-size:12px}:host .order-builder .ob-recap .tot .n{font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 85;font-size:18px;font-variant-numeric:tabular-nums}:host .order-builder .ob-rail-close{display:none}:host .order-builder .ob-fab{display:none;position:fixed;inset-inline-end:20px;bottom:20px;z-index:30;align-items:center;gap:10px;background:var(--ink-active);color:var(--paper);border-radius:var(--r-pill);padding:8px 10px 8px 16px;box-shadow:var(--shadow-3)}:host .order-builder .ob-fab .ft{display:grid;line-height:1.1}:host .order-builder .ob-fab .ft .k{font-size:9.5px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;opacity:.65}:host .order-builder .ob-fab .ft .vv{font-family:var(--font-display);font-variation-settings:\"opsz\" 12,\"wght\" 700,\"wdth\" 82;font-size:16px}:host .order-builder .ob-fab .ft .vv .cur{font-family:var(--font-mono);font-size:.6em;opacity:.7;margin-inline-start:3px}:host .order-builder .ob-fab .open{appearance:none;cursor:pointer;border:0;background:#ffffff24;color:#fff;font:inherit;font-size:12px;font-weight:700;border-radius:var(--r-pill);padding:8px 14px;display:inline-flex;align-items:center;gap:6px}:host .order-builder .ob-fab .open:hover{background:#ffffff3d}:host .order-builder .ob-backdrop{display:none;position:fixed;inset:0;z-index:38;background:#0f11156b;-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px)}:host .order-builder.ob-collapsed .ob-grid{grid-template-columns:minmax(0,1fr)}:host .order-builder.ob-collapsed .ob-fab{display:flex}:host .order-builder.ob-collapsed .ob-rail{position:fixed;top:0;inset-inline-end:0;height:100dvh;width:min(360px,92vw);z-index:40;background:var(--paper);border-inline-start:1px solid var(--rule);box-shadow:var(--shadow-3);padding:14px;overflow:auto;gap:var(--s-3);transform:translate(101%);transition:transform var(--t-base) var(--ease-out)}[dir=rtl] :host .order-builder.ob-collapsed .ob-rail{transform:translate(-101%)}:host .order-builder.ob-collapsed .ob-rail-close{display:inline-flex}:host .order-builder.ob-collapsed.ob-rail-open .ob-rail{transform:none}:host .order-builder.ob-collapsed.ob-rail-open .ob-backdrop{display:block}@media(max-width:1279px){:host .order-builder .ob-grid{grid-template-columns:minmax(0,1fr)}:host .order-builder .ob-fab{display:flex}:host .order-builder .ob-rail{position:fixed;top:0;inset-inline-end:0;height:100dvh;width:min(360px,92vw);z-index:40;background:var(--paper);border-inline-start:1px solid var(--rule);box-shadow:var(--shadow-3);padding:14px;overflow:auto;gap:var(--s-3);transform:translate(101%);transition:transform var(--t-base) var(--ease-out)}[dir=rtl] :host .order-builder .ob-rail{transform:translate(-101%)}:host .order-builder .ob-rail-close{display:inline-flex!important}:host .order-builder.ob-rail-open .ob-rail{transform:none}:host .order-builder.ob-rail-open .ob-backdrop{display:block}}:host .ob-tbl .nm,:host .ob-tbl .num,:host .ob-tbl .ln-val,:host .ob-totals .v,:host .ob-sum-val{unicode-bidi:plaintext}\n"] }]
|
|
4109
4134
|
}], ctorParameters: () => [], propDecorators: { config: [{ type: i0.Input, args: [{ isSignal: true, alias: "config", required: false }] }], order: [{ type: i0.Input, args: [{ isSignal: true, alias: "order", required: true }] }, { type: i0.Output, args: ["orderChange"] }], products: [{ type: i0.Input, args: [{ isSignal: true, alias: "products", required: true }] }], customers: [{ type: i0.Input, args: [{ isSignal: true, alias: "customers", required: true }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], countGroupLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "countGroupLabels", required: false }] }], productKeyField: [{ type: i0.Input, args: [{ isSignal: true, alias: "productKeyField", required: false }] }], backendErrors: [{ type: i0.Input, args: [{ isSignal: true, alias: "backendErrors", required: false }] }], auditEntries: [{ type: i0.Input, args: [{ isSignal: true, alias: "auditEntries", required: false }] }], openCatalogue: [{ type: i0.Output, args: ["openCatalogue"] }], orderChanged: [{ type: i0.Output, args: ["orderChanged"] }], orderValidated: [{ type: i0.Output, args: ["orderValidated"] }], productsAdded: [{ type: i0.Output, args: ["productsAdded"] }], focusMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "focusMode", required: false }] }, { type: i0.Output, args: ["focusModeChange"] }], typeahead: [{ type: i0.ViewChild, args: [i0.forwardRef(() => EfProductTypeaheadComponent), { isSignal: true }] }] } });
|
|
4110
4135
|
|
|
4111
4136
|
/**
|
|
@@ -4130,7 +4155,23 @@ class EfOrderSummaryComponent {
|
|
|
4130
4155
|
/**
|
|
4131
4156
|
* Legend text for the fieldset
|
|
4132
4157
|
*/
|
|
4133
|
-
|
|
4158
|
+
/**
|
|
4159
|
+
* Direct legend text. Prefer `legendKey`: this defaulted to the
|
|
4160
|
+
* French literal 'Récapitulatif', which rendered in French for an
|
|
4161
|
+
* English or Arabic reader.
|
|
4162
|
+
*/
|
|
4163
|
+
translate = inject(TranslateService);
|
|
4164
|
+
legend = input('', ...(ngDevMode ? [{ debugName: "legend" }] : /* istanbul ignore next */ []));
|
|
4165
|
+
/** Translation key for the legend. Wins over `legend`. */
|
|
4166
|
+
legendKey = input('order_summary_legend', ...(ngDevMode ? [{ debugName: "legendKey" }] : /* istanbul ignore next */ []));
|
|
4167
|
+
/**
|
|
4168
|
+
* Translation key for the total row. The label was the hardcoded
|
|
4169
|
+
* English string "Total:" sitting under a French legend default, so
|
|
4170
|
+
* this one component shipped two languages at once and neither
|
|
4171
|
+
* followed the reader.
|
|
4172
|
+
*/
|
|
4173
|
+
totalLabelKey = input('order_summary_total', ...(ngDevMode ? [{ debugName: "totalLabelKey" }] : /* istanbul ignore next */ []));
|
|
4174
|
+
resolvedLegend = computed(() => this.legend()?.trim() ? this.legend() : this.translate.instant(this.legendKey()), ...(ngDevMode ? [{ debugName: "resolvedLegend" }] : /* istanbul ignore next */ []));
|
|
4134
4175
|
/**
|
|
4135
4176
|
* Computed grouped counts by countGroup
|
|
4136
4177
|
*/
|
|
@@ -4200,12 +4241,12 @@ class EfOrderSummaryComponent {
|
|
|
4200
4241
|
*/
|
|
4201
4242
|
hasGroups = computed(() => this.groupedCounts().length > 0, ...(ngDevMode ? [{ debugName: "hasGroups" }] : /* istanbul ignore next */ []));
|
|
4202
4243
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfOrderSummaryComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
4203
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfOrderSummaryComponent, isStandalone: true, selector: "ef-order-summary", inputs: { orderLines: { classPropertyName: "orderLines", publicName: "orderLines", isSignal: true, isRequired: true, transformFunction: null }, countGroupLabels: { classPropertyName: "countGroupLabels", publicName: "countGroupLabels", isSignal: true, isRequired: false, transformFunction: null }, legend: { classPropertyName: "legend", publicName: "legend", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (hasGroups()) {\n <p-fieldset [legend]=\"
|
|
4244
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.11", type: EfOrderSummaryComponent, isStandalone: true, selector: "ef-order-summary", inputs: { orderLines: { classPropertyName: "orderLines", publicName: "orderLines", isSignal: true, isRequired: true, transformFunction: null }, countGroupLabels: { classPropertyName: "countGroupLabels", publicName: "countGroupLabels", isSignal: true, isRequired: false, transformFunction: null }, legend: { classPropertyName: "legend", publicName: "legend", isSignal: true, isRequired: false, transformFunction: null }, legendKey: { classPropertyName: "legendKey", publicName: "legendKey", isSignal: true, isRequired: false, transformFunction: null }, totalLabelKey: { classPropertyName: "totalLabelKey", publicName: "totalLabelKey", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (hasGroups()) {\n <p-fieldset [legend]=\"resolvedLegend()\">\n <div class=\"ef-order-summary\">\n <div class=\"ef-order-summary__content\">\n @for (column of columnGroups(); track $index) {\n <div class=\"ef-order-summary__column\">\n @for (group of column; track group.code) {\n <div class=\"ef-order-summary__item\">\n <span class=\"ef-order-summary__label\">{{ group.label }}:</span>\n <span class=\"ef-order-summary__count\">{{ group.count }}</span>\n </div>\n }\n </div>\n }\n </div>\n <div class=\"ef-order-summary__footer\">\n <span class=\"ef-order-summary__total-label\">{{ totalLabelKey() | translate }}</span>\n <span class=\"ef-order-summary__total-count\">{{ totalCount() }}</span>\n </div>\n </div>\n </p-fieldset>\n}\n", styles: [".ef-order-summary__content{display:flex;gap:2rem;padding:1rem .25rem}.ef-order-summary__column{flex:1;display:flex;flex-direction:column;gap:.25rem}.ef-order-summary__column:not(:last-child){border-right:1px solid var(--p-surface-200);padding-right:2rem}.ef-order-summary__item{display:flex;justify-content:space-between;align-items:center;font-size:.875rem}.ef-order-summary__label{font-weight:600;color:var(--p-surface-700)}:host-context(.dark) .ef-order-summary__label{color:var(--p-surface-300)}.ef-order-summary__count{font-weight:500;color:var(--p-surface-900)}:host-context(.dark) .ef-order-summary__count{color:var(--p-surface-100)}.ef-order-summary__footer{display:flex;justify-content:space-between;align-items:center;padding-top:.75rem;border-top:1px solid var(--p-surface-300)}.ef-order-summary__total-label,.ef-order-summary__total-count{font-size:1rem;font-weight:700;color:var(--p-primary-color)}.ef-order-summary__label,.ef-order-summary__count,.ef-order-summary__total-count{unicode-bidi:plaintext}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FieldsetModule }, { kind: "component", type: i1$7.Fieldset, selector: "p-fieldset", inputs: ["legend", "toggleable", "style", "styleClass", "transitionOptions", "motionOptions", "collapsed"], outputs: ["collapsedChange", "onBeforeToggle", "onAfterToggle"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "pipe", type: i1$1.TranslatePipe, name: "translate" }] });
|
|
4204
4245
|
}
|
|
4205
4246
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.11", ngImport: i0, type: EfOrderSummaryComponent, decorators: [{
|
|
4206
4247
|
type: Component,
|
|
4207
|
-
args: [{ selector: 'ef-order-summary', standalone: true, imports: [CommonModule, FieldsetModule], template: "@if (hasGroups()) {\n <p-fieldset [legend]=\"
|
|
4208
|
-
}], propDecorators: { orderLines: [{ type: i0.Input, args: [{ isSignal: true, alias: "orderLines", required: true }] }], countGroupLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "countGroupLabels", required: false }] }], legend: [{ type: i0.Input, args: [{ isSignal: true, alias: "legend", required: false }] }] } });
|
|
4248
|
+
args: [{ selector: 'ef-order-summary', standalone: true, imports: [CommonModule, FieldsetModule, TranslateModule], template: "@if (hasGroups()) {\n <p-fieldset [legend]=\"resolvedLegend()\">\n <div class=\"ef-order-summary\">\n <div class=\"ef-order-summary__content\">\n @for (column of columnGroups(); track $index) {\n <div class=\"ef-order-summary__column\">\n @for (group of column; track group.code) {\n <div class=\"ef-order-summary__item\">\n <span class=\"ef-order-summary__label\">{{ group.label }}:</span>\n <span class=\"ef-order-summary__count\">{{ group.count }}</span>\n </div>\n }\n </div>\n }\n </div>\n <div class=\"ef-order-summary__footer\">\n <span class=\"ef-order-summary__total-label\">{{ totalLabelKey() | translate }}</span>\n <span class=\"ef-order-summary__total-count\">{{ totalCount() }}</span>\n </div>\n </div>\n </p-fieldset>\n}\n", styles: [".ef-order-summary__content{display:flex;gap:2rem;padding:1rem .25rem}.ef-order-summary__column{flex:1;display:flex;flex-direction:column;gap:.25rem}.ef-order-summary__column:not(:last-child){border-right:1px solid var(--p-surface-200);padding-right:2rem}.ef-order-summary__item{display:flex;justify-content:space-between;align-items:center;font-size:.875rem}.ef-order-summary__label{font-weight:600;color:var(--p-surface-700)}:host-context(.dark) .ef-order-summary__label{color:var(--p-surface-300)}.ef-order-summary__count{font-weight:500;color:var(--p-surface-900)}:host-context(.dark) .ef-order-summary__count{color:var(--p-surface-100)}.ef-order-summary__footer{display:flex;justify-content:space-between;align-items:center;padding-top:.75rem;border-top:1px solid var(--p-surface-300)}.ef-order-summary__total-label,.ef-order-summary__total-count{font-size:1rem;font-weight:700;color:var(--p-primary-color)}.ef-order-summary__label,.ef-order-summary__count,.ef-order-summary__total-count{unicode-bidi:plaintext}\n"] }]
|
|
4249
|
+
}], propDecorators: { orderLines: [{ type: i0.Input, args: [{ isSignal: true, alias: "orderLines", required: true }] }], countGroupLabels: [{ type: i0.Input, args: [{ isSignal: true, alias: "countGroupLabels", required: false }] }], legend: [{ type: i0.Input, args: [{ isSignal: true, alias: "legend", required: false }] }], legendKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "legendKey", required: false }] }], totalLabelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "totalLabelKey", required: false }] }] } });
|
|
4209
4250
|
|
|
4210
4251
|
class EfProductCatalogueFilterComponent {
|
|
4211
4252
|
/** Category groups from reference_data (product_categories items with children) */
|