@hbslovely/baby-feeding 2026.9.2 → 2026.9.3
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.
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, inject, Injectable, signal, input, output, computed, ChangeDetectionStrategy, Component, DestroyRef, HostListener, effect, ViewEncapsulation, ViewChild, EventEmitter, Output, ChangeDetectorRef, Input, ViewChildren } from '@angular/core';
|
|
2
|
+
import { InjectionToken, inject, Injectable, signal, input, output, computed, ChangeDetectionStrategy, Component, DestroyRef, HostListener, effect, ViewEncapsulation, ViewChild, forwardRef, EventEmitter, Output, Pipe, ChangeDetectorRef, Input, ViewChildren } from '@angular/core';
|
|
3
3
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
4
4
|
import * as i1$1 from '@angular/common';
|
|
5
5
|
import { CommonModule, DecimalPipe, NgClass, NgTemplateOutlet } from '@angular/common';
|
|
@@ -8,7 +8,7 @@ import { switchMap, catchError, map, tap, shareReplay, mergeMap, scan, takeLast,
|
|
|
8
8
|
import { throwError, of, from, Subject, interval, firstValueFrom, finalize as finalize$1, catchError as catchError$1, switchMap as switchMap$1, forkJoin, timeout } from 'rxjs';
|
|
9
9
|
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
|
10
10
|
import * as i1 from '@angular/forms';
|
|
11
|
-
import { FormsModule } from '@angular/forms';
|
|
11
|
+
import { FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
12
12
|
import { DomSanitizer } from '@angular/platform-browser';
|
|
13
13
|
|
|
14
14
|
/** Safe defaults when the host app does not provide FEEDING_CONFIG_TOKEN. */
|
|
@@ -57,9 +57,8 @@ const FEEDING_SHEET_RANGES = {
|
|
|
57
57
|
notification: 'Notification!A2:D',
|
|
58
58
|
event: 'Event!A2:G',
|
|
59
59
|
medicalHistory: 'MedicalHistory!A2:H',
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
preparingProductsLegacy: 'Preparing!A2:C',
|
|
60
|
+
/** Danh mục món cần mua (tab Preparing — tách khỏi going-out Settings H:M). */
|
|
61
|
+
preparingProducts: 'Preparing!A2:C',
|
|
63
62
|
preparingRequests: 'Preparing!E2:M',
|
|
64
63
|
preparingComments: 'Preparing!O2:T',
|
|
65
64
|
};
|
|
@@ -77,7 +76,6 @@ const FEEDING_SHEET_TIER1_KEYS = [
|
|
|
77
76
|
/** Loaded when opening settings menu (lazy batchGet). */
|
|
78
77
|
const FEEDING_SHEET_PREPARING_KEYS = [
|
|
79
78
|
'preparingProducts',
|
|
80
|
-
'preparingProductsLegacy',
|
|
81
79
|
'preparingRequests',
|
|
82
80
|
'preparingComments',
|
|
83
81
|
];
|
|
@@ -8513,7 +8511,7 @@ function gapHuman(min) {
|
|
|
8513
8511
|
return `${h} giờ`;
|
|
8514
8512
|
return `${h} giờ ${m} phút`;
|
|
8515
8513
|
}
|
|
8516
|
-
function escapeHtml(s) {
|
|
8514
|
+
function escapeHtml$1(s) {
|
|
8517
8515
|
return s
|
|
8518
8516
|
.replace(/&/g, '&')
|
|
8519
8517
|
.replace(/</g, '<')
|
|
@@ -8522,8 +8520,8 @@ function escapeHtml(s) {
|
|
|
8522
8520
|
}
|
|
8523
8521
|
function buildDayChipsHtml(e) {
|
|
8524
8522
|
return e.dayChips
|
|
8525
|
-
.map((day) => `<div class="pred-day-chip${day.hasSimilar ? ' pred-day-chip--similar' : ''}${day.feedCount === 0 ? ' pred-day-chip--empty' : ''}" title="${escapeHtml(day.dateLabel)} · ${day.feedCount} cữ">` +
|
|
8526
|
-
`<span class="pred-day-chip-date">${escapeHtml(day.dateLabel)}</span>` +
|
|
8523
|
+
.map((day) => `<div class="pred-day-chip${day.hasSimilar ? ' pred-day-chip--similar' : ''}${day.feedCount === 0 ? ' pred-day-chip--empty' : ''}" title="${escapeHtml$1(day.dateLabel)} · ${day.feedCount} cữ">` +
|
|
8524
|
+
`<span class="pred-day-chip-date">${escapeHtml$1(day.dateLabel)}</span>` +
|
|
8527
8525
|
`<span class="pred-day-chip-count">${day.feedCount}</span></div>`)
|
|
8528
8526
|
.join('');
|
|
8529
8527
|
}
|
|
@@ -8532,11 +8530,11 @@ function buildSimilarPairsHtml(e) {
|
|
|
8532
8530
|
return '';
|
|
8533
8531
|
const items = e.similarPairs
|
|
8534
8532
|
.map((pair) => `<li class="pred-pair-item">` +
|
|
8535
|
-
`<div class="pred-pair-when"><span class="pred-pair-date">${escapeHtml(pair.dateLabel)}</span>` +
|
|
8536
|
-
`<span class="pred-pair-time">${escapeHtml(pair.prevTime)}</span></div>` +
|
|
8533
|
+
`<div class="pred-pair-when"><span class="pred-pair-date">${escapeHtml$1(pair.dateLabel)}</span>` +
|
|
8534
|
+
`<span class="pred-pair-time">${escapeHtml$1(pair.prevTime)}</span></div>` +
|
|
8537
8535
|
`<div class="pred-pair-arrow" aria-hidden="true"><span class="pred-pair-wait">${waitHuman(pair.waitMin)}</span>` +
|
|
8538
8536
|
`<i class="pi pi-arrow-right"></i></div>` +
|
|
8539
|
-
`<div class="pred-pair-next"><span class="pred-pair-time">${escapeHtml(pair.nextTime)}</span>` +
|
|
8537
|
+
`<div class="pred-pair-next"><span class="pred-pair-time">${escapeHtml$1(pair.nextTime)}</span>` +
|
|
8540
8538
|
`<span class="pred-pair-ml">${pair.nextVolume}ml</span></div></li>`)
|
|
8541
8539
|
.join('');
|
|
8542
8540
|
return `<ul class="pred-pair-list">${items}</ul>`;
|
|
@@ -8546,9 +8544,9 @@ function buildWeightedPairsHtml(e) {
|
|
|
8546
8544
|
if (!pairs.length)
|
|
8547
8545
|
return '';
|
|
8548
8546
|
const rows = pairs
|
|
8549
|
-
.map((p) => `<tr><td>${escapeHtml(p.dateLabel)} ${escapeHtml(p.prevTime)}</td>` +
|
|
8547
|
+
.map((p) => `<tr><td>${escapeHtml$1(p.dateLabel)} ${escapeHtml$1(p.prevTime)}</td>` +
|
|
8550
8548
|
`<td>${p.prevVolume}ml</td><td>${waitHuman(p.waitMin)}</td>` +
|
|
8551
|
-
`<td>${p.weight}</td><td>${escapeHtml(p.flags.join(', ') || '—')}</td></tr>`)
|
|
8549
|
+
`<td>${p.weight}</td><td>${escapeHtml$1(p.flags.join(', ') || '—')}</td></tr>`)
|
|
8552
8550
|
.join('');
|
|
8553
8551
|
return (`<table class="pred-weight-table"><thead><tr>` +
|
|
8554
8552
|
`<th>Cặp</th><th>ml</th><th>Chờ</th><th>Trọng số</th><th>Ghi chú</th>` +
|
|
@@ -8561,7 +8559,7 @@ function buildVolumeSamplesHtml(e, maxVol) {
|
|
|
8561
8559
|
.map((sample) => {
|
|
8562
8560
|
const pct = Math.max(8, Math.round((sample.volume / maxVol) * 100));
|
|
8563
8561
|
return (`<div class="pred-vol-row">` +
|
|
8564
|
-
`<span class="pred-vol-meta">${escapeHtml(sample.dateLabel)} · ${escapeHtml(sample.time)}</span>` +
|
|
8562
|
+
`<span class="pred-vol-meta">${escapeHtml$1(sample.dateLabel)} · ${escapeHtml$1(sample.time)}</span>` +
|
|
8565
8563
|
`<div class="pred-vol-track"><div class="pred-vol-fill" style="width:${pct}%"></div></div>` +
|
|
8566
8564
|
`<span class="pred-vol-ml">${sample.volume}ml</span></div>`);
|
|
8567
8565
|
})
|
|
@@ -8571,7 +8569,7 @@ function buildConfidenceChecksHtml(e) {
|
|
|
8571
8569
|
return e.confidenceChecks
|
|
8572
8570
|
.map((check) => `<li class="${check.ok ? 'pred-check--ok' : 'pred-check--no'}">` +
|
|
8573
8571
|
`<i class="pi ${check.ok ? 'pi-check-circle' : 'pi-times-circle'}"></i> ` +
|
|
8574
|
-
`${escapeHtml(check.label)}</li>`)
|
|
8572
|
+
`${escapeHtml$1(check.label)}</li>`)
|
|
8575
8573
|
.join('');
|
|
8576
8574
|
}
|
|
8577
8575
|
/** Xây map biến cho bindTemplate từ prediction hiện tại. */
|
|
@@ -9333,7 +9331,6 @@ class FeedingDailyComponent {
|
|
|
9333
9331
|
{ ml: 100, icon: 'fa-droplet' },
|
|
9334
9332
|
{ ml: 110, icon: 'fa-glass-water' },
|
|
9335
9333
|
{ ml: 120, icon: 'fa-mug-saucer' },
|
|
9336
|
-
{ ml: 130, icon: 'fa-bottle-droplet' },
|
|
9337
9334
|
{ ml: 150, icon: 'fa-bottle-water' },
|
|
9338
9335
|
{ ml: 160, icon: 'fa-mug-hot' },
|
|
9339
9336
|
{ ml: 180, icon: 'fa-fill-drip' },
|
|
@@ -10925,6 +10922,184 @@ function viFoldIncludes(haystack, needle) {
|
|
|
10925
10922
|
return foldViKey(haystack).includes(n);
|
|
10926
10923
|
}
|
|
10927
10924
|
|
|
10925
|
+
const ALLOWED_TAGS = new Set([
|
|
10926
|
+
'B',
|
|
10927
|
+
'STRONG',
|
|
10928
|
+
'I',
|
|
10929
|
+
'EM',
|
|
10930
|
+
'U',
|
|
10931
|
+
'UL',
|
|
10932
|
+
'OL',
|
|
10933
|
+
'LI',
|
|
10934
|
+
'P',
|
|
10935
|
+
'BR',
|
|
10936
|
+
'DIV',
|
|
10937
|
+
'SPAN',
|
|
10938
|
+
]);
|
|
10939
|
+
/** Escape plain text for safe HTML insertion. */
|
|
10940
|
+
function escapeHtml(text) {
|
|
10941
|
+
return text
|
|
10942
|
+
.replace(/&/g, '&')
|
|
10943
|
+
.replace(/</g, '<')
|
|
10944
|
+
.replace(/>/g, '>')
|
|
10945
|
+
.replace(/"/g, '"')
|
|
10946
|
+
.replace(/'/g, ''');
|
|
10947
|
+
}
|
|
10948
|
+
/** Strip all HTML tags → plain text (search / fallback). */
|
|
10949
|
+
function stripHtml(html) {
|
|
10950
|
+
if (!html)
|
|
10951
|
+
return '';
|
|
10952
|
+
if (typeof document === 'undefined') {
|
|
10953
|
+
return html.replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
|
|
10954
|
+
}
|
|
10955
|
+
const doc = new DOMParser().parseFromString(html, 'text/html');
|
|
10956
|
+
return (doc.body.textContent || '').replace(/\s+/g, ' ').trim();
|
|
10957
|
+
}
|
|
10958
|
+
function looksLikeHtml(value) {
|
|
10959
|
+
return /<[a-z][\s\S]*>/i.test(value);
|
|
10960
|
+
}
|
|
10961
|
+
function serializeAllowed(node) {
|
|
10962
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
10963
|
+
return escapeHtml(node.textContent ?? '');
|
|
10964
|
+
}
|
|
10965
|
+
if (node.nodeType !== Node.ELEMENT_NODE) {
|
|
10966
|
+
return '';
|
|
10967
|
+
}
|
|
10968
|
+
const el = node;
|
|
10969
|
+
const tag = el.tagName.toUpperCase();
|
|
10970
|
+
if (tag === 'BR') {
|
|
10971
|
+
return '<br>';
|
|
10972
|
+
}
|
|
10973
|
+
const inner = Array.from(el.childNodes).map(serializeAllowed).join('');
|
|
10974
|
+
if (!ALLOWED_TAGS.has(tag)) {
|
|
10975
|
+
return inner;
|
|
10976
|
+
}
|
|
10977
|
+
const lower = tag.toLowerCase();
|
|
10978
|
+
if (tag === 'DIV' || tag === 'P' || tag === 'LI') {
|
|
10979
|
+
return `<${lower}>${inner}</${lower}>`;
|
|
10980
|
+
}
|
|
10981
|
+
return `<${lower}>${inner}</${lower}>`;
|
|
10982
|
+
}
|
|
10983
|
+
/**
|
|
10984
|
+
* Sanitize rich-text HTML for storage / display.
|
|
10985
|
+
* Plain text (legacy) is escaped and newlines become `<br>`.
|
|
10986
|
+
*/
|
|
10987
|
+
function sanitizeRichHtml(value) {
|
|
10988
|
+
const raw = (value ?? '').trim();
|
|
10989
|
+
if (!raw)
|
|
10990
|
+
return '';
|
|
10991
|
+
if (!looksLikeHtml(raw)) {
|
|
10992
|
+
return escapeHtml(raw).replace(/\r\n|\r|\n/g, '<br>');
|
|
10993
|
+
}
|
|
10994
|
+
if (typeof document === 'undefined') {
|
|
10995
|
+
return escapeHtml(stripHtml(raw));
|
|
10996
|
+
}
|
|
10997
|
+
const doc = new DOMParser().parseFromString(raw, 'text/html');
|
|
10998
|
+
const html = Array.from(doc.body.childNodes).map(serializeAllowed).join('');
|
|
10999
|
+
const text = stripHtml(html);
|
|
11000
|
+
return text ? html : '';
|
|
11001
|
+
}
|
|
11002
|
+
/** True when editor HTML is effectively empty. */
|
|
11003
|
+
function isEmptyRichHtml(html) {
|
|
11004
|
+
return !stripHtml(html ?? '');
|
|
11005
|
+
}
|
|
11006
|
+
|
|
11007
|
+
class RichTextEditorComponent {
|
|
11008
|
+
constructor() {
|
|
11009
|
+
this.placeholder = input('Nhập nội dung…', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
11010
|
+
/** Min height of the editable area (px). */
|
|
11011
|
+
this.minHeight = input(160, ...(ngDevMode ? [{ debugName: "minHeight" }] : /* istanbul ignore next */ []));
|
|
11012
|
+
this.disabled = signal(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
11013
|
+
this.focused = signal(false, ...(ngDevMode ? [{ debugName: "focused" }] : /* istanbul ignore next */ []));
|
|
11014
|
+
this.onChange = () => undefined;
|
|
11015
|
+
this.onTouched = () => undefined;
|
|
11016
|
+
this.lastEmitted = '';
|
|
11017
|
+
this.pendingHtml = null;
|
|
11018
|
+
this.viewReady = false;
|
|
11019
|
+
}
|
|
11020
|
+
ngAfterViewInit() {
|
|
11021
|
+
this.viewReady = true;
|
|
11022
|
+
this.applyPendingHtml();
|
|
11023
|
+
}
|
|
11024
|
+
writeValue(value) {
|
|
11025
|
+
const html = sanitizeRichHtml(value ?? '');
|
|
11026
|
+
this.lastEmitted = html;
|
|
11027
|
+
this.pendingHtml = html;
|
|
11028
|
+
if (this.viewReady) {
|
|
11029
|
+
this.applyPendingHtml();
|
|
11030
|
+
}
|
|
11031
|
+
}
|
|
11032
|
+
registerOnChange(fn) {
|
|
11033
|
+
this.onChange = fn;
|
|
11034
|
+
}
|
|
11035
|
+
registerOnTouched(fn) {
|
|
11036
|
+
this.onTouched = fn;
|
|
11037
|
+
}
|
|
11038
|
+
setDisabledState(isDisabled) {
|
|
11039
|
+
this.disabled.set(isDisabled);
|
|
11040
|
+
}
|
|
11041
|
+
onInput() {
|
|
11042
|
+
this.emitValue();
|
|
11043
|
+
}
|
|
11044
|
+
onFocus() {
|
|
11045
|
+
this.focused.set(true);
|
|
11046
|
+
}
|
|
11047
|
+
onBlur() {
|
|
11048
|
+
this.focused.set(false);
|
|
11049
|
+
this.onTouched();
|
|
11050
|
+
this.emitValue();
|
|
11051
|
+
}
|
|
11052
|
+
exec(command, event) {
|
|
11053
|
+
event.preventDefault();
|
|
11054
|
+
if (this.disabled())
|
|
11055
|
+
return;
|
|
11056
|
+
this.editorRef?.nativeElement.focus();
|
|
11057
|
+
document.execCommand(command, false);
|
|
11058
|
+
this.emitValue();
|
|
11059
|
+
}
|
|
11060
|
+
applyPendingHtml() {
|
|
11061
|
+
const el = this.editorRef?.nativeElement;
|
|
11062
|
+
if (!el || this.pendingHtml === null)
|
|
11063
|
+
return;
|
|
11064
|
+
if (el.innerHTML !== this.pendingHtml) {
|
|
11065
|
+
el.innerHTML = this.pendingHtml;
|
|
11066
|
+
}
|
|
11067
|
+
this.pendingHtml = null;
|
|
11068
|
+
}
|
|
11069
|
+
emitValue() {
|
|
11070
|
+
const el = this.editorRef?.nativeElement;
|
|
11071
|
+
if (!el)
|
|
11072
|
+
return;
|
|
11073
|
+
const raw = el.innerHTML;
|
|
11074
|
+
const html = isEmptyRichHtml(raw) ? '' : sanitizeRichHtml(raw);
|
|
11075
|
+
if (html === this.lastEmitted)
|
|
11076
|
+
return;
|
|
11077
|
+
this.lastEmitted = html;
|
|
11078
|
+
this.onChange(html);
|
|
11079
|
+
}
|
|
11080
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: RichTextEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
11081
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.17", type: RichTextEditorComponent, isStandalone: true, selector: "app-rich-text-editor", inputs: { placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, minHeight: { classPropertyName: "minHeight", publicName: "minHeight", isSignal: true, isRequired: false, transformFunction: null } }, providers: [
|
|
11082
|
+
{
|
|
11083
|
+
provide: NG_VALUE_ACCESSOR,
|
|
11084
|
+
useExisting: forwardRef(() => RichTextEditorComponent),
|
|
11085
|
+
multi: true,
|
|
11086
|
+
},
|
|
11087
|
+
], viewQueries: [{ propertyName: "editorRef", first: true, predicate: ["editor"], descendants: true }], ngImport: i0, template: "<div\n class=\"rte\"\n [class.rte--focused]=\"focused()\"\n [class.rte--disabled]=\"disabled()\"\n [style.--rte-min-h.px]=\"minHeight()\"\n>\n <div class=\"rte__toolbar\" role=\"toolbar\" aria-label=\"\u0110\u1ECBnh d\u1EA1ng v\u0103n b\u1EA3n\">\n <button\n type=\"button\"\n class=\"rte__btn\"\n title=\"\u0110\u1EADm\"\n aria-label=\"\u0110\u1EADm\"\n (mousedown)=\"exec('bold', $event)\"\n [disabled]=\"disabled()\"\n >\n <i class=\"fa-solid fa-bold\" aria-hidden=\"true\"></i>\n </button>\n <button\n type=\"button\"\n class=\"rte__btn\"\n title=\"Nghi\u00EAng\"\n aria-label=\"Nghi\u00EAng\"\n (mousedown)=\"exec('italic', $event)\"\n [disabled]=\"disabled()\"\n >\n <i class=\"fa-solid fa-italic\" aria-hidden=\"true\"></i>\n </button>\n <button\n type=\"button\"\n class=\"rte__btn\"\n title=\"G\u1EA1ch d\u01B0\u1EDBi\"\n aria-label=\"G\u1EA1ch d\u01B0\u1EDBi\"\n (mousedown)=\"exec('underline', $event)\"\n [disabled]=\"disabled()\"\n >\n <i class=\"fa-solid fa-underline\" aria-hidden=\"true\"></i>\n </button>\n <span class=\"rte__sep\" aria-hidden=\"true\"></span>\n <button\n type=\"button\"\n class=\"rte__btn\"\n title=\"Danh s\u00E1ch\"\n aria-label=\"Danh s\u00E1ch\"\n (mousedown)=\"exec('insertUnorderedList', $event)\"\n [disabled]=\"disabled()\"\n >\n <i class=\"fa-solid fa-list-ul\" aria-hidden=\"true\"></i>\n </button>\n <button\n type=\"button\"\n class=\"rte__btn\"\n title=\"Danh s\u00E1ch s\u1ED1\"\n aria-label=\"Danh s\u00E1ch s\u1ED1\"\n (mousedown)=\"exec('insertOrderedList', $event)\"\n [disabled]=\"disabled()\"\n >\n <i class=\"fa-solid fa-list-ol\" aria-hidden=\"true\"></i>\n </button>\n </div>\n <div\n #editor\n class=\"rte__editor\"\n role=\"textbox\"\n aria-multiline=\"true\"\n [attr.data-placeholder]=\"placeholder()\"\n [attr.contenteditable]=\"disabled() ? 'false' : 'true'\"\n (input)=\"onInput()\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n ></div>\n</div>\n", styles: [".rte{display:flex;flex-direction:column;border:1px solid var(--border, #e5e7eb);background:var(--surface, #fff);overflow:hidden}.rte--focused{border-color:var(--primary, #5b6cff);box-shadow:0 0 0 3px color-mix(in srgb,var(--primary, #5b6cff) 22%,transparent)}.rte--disabled{opacity:.65;pointer-events:none}.rte__toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:2px;padding:6px 8px;border-bottom:1px solid var(--border, #e5e7eb);background:var(--surface-2, #f8fafc)}.rte__btn{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;padding:0;border:none;border-radius:6px;background:transparent;color:var(--text, #0f172a);cursor:pointer;font-size:13px}.rte__btn:hover:not(:disabled){background:color-mix(in srgb,var(--primary, #5b6cff) 12%,white)}.rte__btn:disabled{opacity:.45;cursor:not-allowed}.rte__sep{width:1px;height:18px;margin:0 4px;background:var(--border, #e5e7eb)}.rte__editor{min-height:var(--rte-min-h, 160px);max-height:min(42vh,320px);overflow-y:auto;padding:10px 12px;font-size:15px;line-height:1.5;font-family:inherit;color:var(--text, #0f172a);outline:none;word-break:break-word}.rte__editor:empty:before{content:attr(data-placeholder);color:var(--muted-2, #94a3b8);pointer-events:none}.rte__editor ul,.rte__editor ol{margin:.35em 0;padding-left:1.35em}.rte__editor p{margin:0 0 .4em}.rte__editor p:last-child{margin-bottom:0}.rte__editor b,.rte__editor strong{font-weight:700}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
11088
|
+
}
|
|
11089
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: RichTextEditorComponent, decorators: [{
|
|
11090
|
+
type: Component,
|
|
11091
|
+
args: [{ selector: 'app-rich-text-editor', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, providers: [
|
|
11092
|
+
{
|
|
11093
|
+
provide: NG_VALUE_ACCESSOR,
|
|
11094
|
+
useExisting: forwardRef(() => RichTextEditorComponent),
|
|
11095
|
+
multi: true,
|
|
11096
|
+
},
|
|
11097
|
+
], template: "<div\n class=\"rte\"\n [class.rte--focused]=\"focused()\"\n [class.rte--disabled]=\"disabled()\"\n [style.--rte-min-h.px]=\"minHeight()\"\n>\n <div class=\"rte__toolbar\" role=\"toolbar\" aria-label=\"\u0110\u1ECBnh d\u1EA1ng v\u0103n b\u1EA3n\">\n <button\n type=\"button\"\n class=\"rte__btn\"\n title=\"\u0110\u1EADm\"\n aria-label=\"\u0110\u1EADm\"\n (mousedown)=\"exec('bold', $event)\"\n [disabled]=\"disabled()\"\n >\n <i class=\"fa-solid fa-bold\" aria-hidden=\"true\"></i>\n </button>\n <button\n type=\"button\"\n class=\"rte__btn\"\n title=\"Nghi\u00EAng\"\n aria-label=\"Nghi\u00EAng\"\n (mousedown)=\"exec('italic', $event)\"\n [disabled]=\"disabled()\"\n >\n <i class=\"fa-solid fa-italic\" aria-hidden=\"true\"></i>\n </button>\n <button\n type=\"button\"\n class=\"rte__btn\"\n title=\"G\u1EA1ch d\u01B0\u1EDBi\"\n aria-label=\"G\u1EA1ch d\u01B0\u1EDBi\"\n (mousedown)=\"exec('underline', $event)\"\n [disabled]=\"disabled()\"\n >\n <i class=\"fa-solid fa-underline\" aria-hidden=\"true\"></i>\n </button>\n <span class=\"rte__sep\" aria-hidden=\"true\"></span>\n <button\n type=\"button\"\n class=\"rte__btn\"\n title=\"Danh s\u00E1ch\"\n aria-label=\"Danh s\u00E1ch\"\n (mousedown)=\"exec('insertUnorderedList', $event)\"\n [disabled]=\"disabled()\"\n >\n <i class=\"fa-solid fa-list-ul\" aria-hidden=\"true\"></i>\n </button>\n <button\n type=\"button\"\n class=\"rte__btn\"\n title=\"Danh s\u00E1ch s\u1ED1\"\n aria-label=\"Danh s\u00E1ch s\u1ED1\"\n (mousedown)=\"exec('insertOrderedList', $event)\"\n [disabled]=\"disabled()\"\n >\n <i class=\"fa-solid fa-list-ol\" aria-hidden=\"true\"></i>\n </button>\n </div>\n <div\n #editor\n class=\"rte__editor\"\n role=\"textbox\"\n aria-multiline=\"true\"\n [attr.data-placeholder]=\"placeholder()\"\n [attr.contenteditable]=\"disabled() ? 'false' : 'true'\"\n (input)=\"onInput()\"\n (focus)=\"onFocus()\"\n (blur)=\"onBlur()\"\n ></div>\n</div>\n", styles: [".rte{display:flex;flex-direction:column;border:1px solid var(--border, #e5e7eb);background:var(--surface, #fff);overflow:hidden}.rte--focused{border-color:var(--primary, #5b6cff);box-shadow:0 0 0 3px color-mix(in srgb,var(--primary, #5b6cff) 22%,transparent)}.rte--disabled{opacity:.65;pointer-events:none}.rte__toolbar{display:flex;flex-wrap:wrap;align-items:center;gap:2px;padding:6px 8px;border-bottom:1px solid var(--border, #e5e7eb);background:var(--surface-2, #f8fafc)}.rte__btn{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;padding:0;border:none;border-radius:6px;background:transparent;color:var(--text, #0f172a);cursor:pointer;font-size:13px}.rte__btn:hover:not(:disabled){background:color-mix(in srgb,var(--primary, #5b6cff) 12%,white)}.rte__btn:disabled{opacity:.45;cursor:not-allowed}.rte__sep{width:1px;height:18px;margin:0 4px;background:var(--border, #e5e7eb)}.rte__editor{min-height:var(--rte-min-h, 160px);max-height:min(42vh,320px);overflow-y:auto;padding:10px 12px;font-size:15px;line-height:1.5;font-family:inherit;color:var(--text, #0f172a);outline:none;word-break:break-word}.rte__editor:empty:before{content:attr(data-placeholder);color:var(--muted-2, #94a3b8);pointer-events:none}.rte__editor ul,.rte__editor ol{margin:.35em 0;padding-left:1.35em}.rte__editor p{margin:0 0 .4em}.rte__editor p:last-child{margin-bottom:0}.rte__editor b,.rte__editor strong{font-weight:700}\n"] }]
|
|
11098
|
+
}], propDecorators: { placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], minHeight: [{ type: i0.Input, args: [{ isSignal: true, alias: "minHeight", required: false }] }], editorRef: [{
|
|
11099
|
+
type: ViewChild,
|
|
11100
|
+
args: ['editor']
|
|
11101
|
+
}] } });
|
|
11102
|
+
|
|
10928
11103
|
/**
|
|
10929
11104
|
* Gợi ý nơi khám — có thể chọn nhanh hoặc gõ thêm (lưu cục bộ theo user).
|
|
10930
11105
|
*/
|
|
@@ -11197,7 +11372,7 @@ class MedicalHistoryDialogComponent {
|
|
|
11197
11372
|
date: d.date,
|
|
11198
11373
|
kind: d.kind,
|
|
11199
11374
|
title,
|
|
11200
|
-
detail: d.detail
|
|
11375
|
+
detail: sanitizeRichHtml(d.detail),
|
|
11201
11376
|
place: d.place.trim() || undefined,
|
|
11202
11377
|
driveFileId,
|
|
11203
11378
|
};
|
|
@@ -11384,11 +11559,11 @@ class MedicalHistoryDialogComponent {
|
|
|
11384
11559
|
return Math.max(0, Math.floor((payload.length * 3) / 4) - padding);
|
|
11385
11560
|
}
|
|
11386
11561
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: MedicalHistoryDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
11387
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: MedicalHistoryDialogComponent, isStandalone: true, selector: "app-medical-history-dialog", inputs: { user: { classPropertyName: "user", publicName: "user", isSignal: true, isRequired: false, transformFunction: null }, explorerEntries: { classPropertyName: "explorerEntries", publicName: "explorerEntries", isSignal: true, isRequired: false, transformFunction: null }, medicalFolderId: { classPropertyName: "medicalFolderId", publicName: "medicalFolderId", isSignal: true, isRequired: false, transformFunction: null }, entries: { classPropertyName: "entries", publicName: "entries", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", saved: "saved" }, host: { listeners: { "document:click": "onDocumentClick($event)" } }, ngImport: i0, template: "@if (dialogOpen()) {\n <div class=\"dialog-overlay\" (click)=\"closeDialog()\">\n <div\n class=\"dialog medical-dialog\"\n role=\"dialog\"\n aria-modal=\"true\"\n (click)=\"$event.stopPropagation()\"\n >\n <div class=\"dialog-head\">\n <div>\n <h2>\n <i class=\"pi pi-pencil\"></i>\n {{ editingEntry() ? 'S\u1EEDa s\u1EF1 ki\u1EC7n' : 'Th\u00EAm s\u1EF1 ki\u1EC7n' }}\n </h2>\n </div>\n <button type=\"button\" class=\"dialog-close\" (click)=\"closeDialog()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n @if (errorMsg()) {\n <div class=\"alert error\">\n <i class=\"pi pi-exclamation-circle\"></i>\n {{ errorMsg() }}\n </div>\n }\n <form class=\"medical-form\" (ngSubmit)=\"submitDialog()\">\n <label class=\"field\">\n <span class=\"label\">Ng\u00E0y</span>\n <input\n type=\"date\"\n [ngModel]=\"draft().date\"\n (ngModelChange)=\"updateDraft({ date: $event })\"\n name=\"mhDate\"\n [max]=\"maxDateToday\"\n required\n />\n </label>\n <div class=\"field mh-combo-field\">\n <span class=\"label\">Lo\u1EA1i</span>\n <div class=\"mh-combo\" [class.open]=\"kindPickerOpen()\">\n <button\n type=\"button\"\n class=\"mh-combo-trigger\"\n (click)=\"toggleKindPicker(); $event.stopPropagation()\"\n >\n <span class=\"mh-combo-trigger__icon\"\n ><i class=\"fa-solid\" [ngClass]=\"metaFor(draft().kind).icon\"></i\n ></span>\n <span class=\"mh-combo-trigger__text\">{{ metaFor(draft().kind).label }}</span>\n <i class=\"pi pi-chevron-down mh-combo-chev\"></i>\n </button>\n @if (kindPickerOpen()) {\n <div class=\"mh-combo-panel\" (click)=\"$event.stopPropagation()\">\n <input\n type=\"search\"\n class=\"mh-combo-search\"\n placeholder=\"T\u00ECm lo\u1EA1i\u2026\"\n [ngModel]=\"kindSearch()\"\n (ngModelChange)=\"kindSearch.set($event)\"\n name=\"mhKindSearch\"\n autocomplete=\"off\"\n (click)=\"$event.stopPropagation()\"\n />\n <ul class=\"mh-combo-list\" role=\"listbox\">\n @for (km of filteredKindCatalog(); track km) {\n <li\n role=\"option\"\n [class.active]=\"draft().kind === km.id\"\n (click)=\"selectKind(km.id)\"\n >\n <i class=\"fa-solid\" [ngClass]=\"km.icon\"></i> {{ km.label }}\n </li>\n }\n </ul>\n </div>\n }\n </div>\n </div>\n <label class=\"field\">\n <span class=\"label\">Ti\u00EAu \u0111\u1EC1</span>\n <input\n type=\"text\"\n [ngModel]=\"draft().title\"\n (ngModelChange)=\"updateDraft({ title: $event })\"\n name=\"mhTitle\"\n placeholder=\"VD: M\u0169i 2 Pentaxim \u2014 BV Nhi\"\n required\n />\n </label>\n <label class=\"field\">\n <span class=\"label\">Chi ti\u1EBFt</span>\n <textarea\n [ngModel]=\"draft().detail\"\n (ngModelChange)=\"updateDraft({ detail: $event })\"\n name=\"mhDetail\"\n rows=\"3\"\n placeholder=\"Ph\u1EA3n \u1EE9ng, ch\u1EC9 \u0111\u1ECBnh BS, li\u1EC1u\u2026\"\n ></textarea>\n </label>\n <div class=\"field mh-combo-field\">\n <span class=\"label\">N\u01A1i kh\u00E1m</span>\n <div class=\"mh-combo mh-place-combo\" [class.open]=\"placePickerOpen()\">\n <input\n type=\"text\"\n class=\"mh-combo-input\"\n [ngModel]=\"draft().place\"\n (ngModelChange)=\"\n updateDraft({ place: $event }); kindPickerOpen.set(false); placePickerOpen.set(true)\n \"\n (focus)=\"onPlaceFieldFocus()\"\n name=\"mhPlace\"\n placeholder=\"Ch\u1ECDn ho\u1EB7c g\u00F5 t\u00EAn c\u01A1 s\u1EDF\u2026\"\n autocomplete=\"off\"\n />\n @if (placePickerOpen()) {\n <div class=\"mh-combo-panel\" (click)=\"$event.stopPropagation()\">\n @if (canAddCustomPlace()) {\n <button type=\"button\" class=\"mh-combo-add\" (click)=\"addCustomPlaceFromDraft()\">\n <i class=\"pi pi-plus\"></i> Th\u00EAm \u00AB{{ draft().place.trim() }}\u00BB v\u00E0o danh s\u00E1ch\n </button>\n }\n <ul class=\"mh-combo-list mh-combo-list--places\">\n @for (p of filteredPlaceSuggestions(); track p) {\n <li (click)=\"selectPlace(p)\">\n {{ p }}\n </li>\n }\n </ul>\n </div>\n }\n </div>\n </div>\n <div class=\"field med-attach-field\">\n <span class=\"label\">\u1EA2nh \u0111\u00EDnh k\u00E8m</span>\n @if (\n editingEntry() &&\n !pendingAttachmentPreviewUrl() &&\n !stripExistingAttachment() &&\n attachmentFor(editingEntry()!);\n as sav\n ) {\n <div class=\"med-preview med-preview--saved\">\n @if (sav.content || sav.previewUrl) {\n <img [src]=\"sav.previewUrl || sav.content\" alt=\"\u0110\u00EDnh k\u00E8m hi\u1EC7n t\u1EA1i\" />\n }\n <button type=\"button\" class=\"btn ghost sm\" (click)=\"removeSavedAttachment()\">\n <i class=\"pi pi-times\"></i> G\u1EE1 \u1EA3nh \u0111\u00E3 l\u01B0u\n </button>\n </div>\n }\n @if (pendingAttachmentPreviewUrl(); as prevUrl) {\n <div class=\"med-preview med-preview--new\">\n <img [src]=\"prevUrl\" alt=\"Xem tr\u01B0\u1EDBc\" />\n <button type=\"button\" class=\"btn ghost sm\" (click)=\"clearPendingAttachment()\">\n Hu\u1EF7 \u1EA3nh m\u1EDBi\n </button>\n </div>\n }\n <label class=\"med-upload-card\">\n <input\n type=\"file\"\n accept=\"image/*,.heic,.heif\"\n class=\"sr-only\"\n (change)=\"onAttachmentInput($any($event.target))\"\n />\n <span class=\"med-upload-card__icon\" aria-hidden=\"true\">\n <i class=\"pi pi-images\"></i>\n </span>\n <span class=\"med-upload-card__text\">\n <strong class=\"med-upload-card__title\">{{\n pendingAttachment() ? '\u0110\u1ED5i \u1EA3nh' : 'Ch\u1ECDn \u1EA3nh'\n }}</strong>\n </span>\n <span class=\"med-upload-card__arrow\" aria-hidden=\"true\">\n <i class=\"pi pi-angle-right\"></i>\n </span>\n </label>\n </div>\n <div class=\"dialog-actions\">\n <button type=\"button\" class=\"btn ghost\" (click)=\"closeDialog()\" [disabled]=\"saving()\">\n Hu\u1EF7\n </button>\n <button type=\"submit\" class=\"btn primary\" [disabled]=\"saving()\">\n @if (!saving()) {\n <i class=\"pi pi-check\"></i>\n }\n @if (saving()) {\n <i class=\"pi pi-spin pi-spinner\"></i>\n }\n {{ editingEntry() ? 'C\u1EADp nh\u1EADt' : 'L\u01B0u' }}\n </button>\n </div>\n </form>\n </div>\n </div>\n}\n", styles: ["@charset \"UTF-8\";.dialog-overlay{position:fixed;inset:0;z-index:250;background:#0f172a73;display:flex;align-items:center;justify-content:center;padding:16px;padding-top:max(16px,env(safe-area-inset-top,0px));padding-bottom:max(16px,env(safe-area-inset-bottom,0px));box-sizing:border-box}@media(max-width:639px){.dialog-overlay{padding-bottom:calc(68px + env(safe-area-inset-bottom,0px));align-items:center}}.medical-dialog{width:100%;max-width:480px;max-height:90vh;overflow:auto;background:var(--surface, #fff);border-radius:0;padding:0 0 12px;box-shadow:0 24px 48px -24px #0f172a59}@media(min-width:640px){.medical-dialog{max-height:min(88dvh,720px);padding-bottom:env(safe-area-inset-bottom,12px)}}.medical-dialog .field input,.medical-dialog .field select,.medical-dialog .field textarea,.medical-dialog .mh-combo-trigger,.medical-dialog .mh-combo-input{padding:8px 10px;border-radius:0}.medical-dialog .mh-combo-panel,.medical-dialog .mh-combo-search,.medical-dialog .med-upload-card{border-radius:0}.medical-dialog .alert.error{display:flex;align-items:flex-start;gap:10px;margin:0 16px 8px;padding:10px 12px;border-radius:12px;font-size:13px;background:#fef2f2;color:#b91c1c;border:1px solid #fecaca}.dialog-head{display:flex;justify-content:space-between;align-items:flex-start;gap:12px;padding:14px 16px 10px;border-bottom:1px solid var(--border, #e5e7eb)}.dialog-head h2{margin:0;font-size:1.1rem;font-weight:800;display:flex;align-items:center;gap:8px}.dialog-close{border:none;background:transparent;padding:8px;cursor:pointer;color:var(--muted);border-radius:10px}.dialog-close:hover{background:var(--surface-2, #f1f5f9)}.medical-form{padding:12px 16px 16px;display:flex;flex-direction:column;gap:10px}.field{display:flex;flex-direction:column;gap:4px}.field .label{font-size:12px;font-weight:700;color:var(--muted)}.field input,.field select,.field textarea{padding:10px 12px;border-radius:12px;border:1px solid var(--border, #e5e7eb);font-size:15px;font-family:inherit;background:var(--surface, #fff)}.field textarea{resize:vertical;min-height:72px}.dialog-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:4px;padding-top:4px}.btn.ghost{border:1px solid var(--border);background:transparent;padding:10px 18px;border-radius:12px;font-weight:700;cursor:pointer;font-family:inherit}.btn.ghost.sm{padding:6px 12px;font-size:12px;border-radius:10px}.btn.primary{background:var(--primary, #5b6cff);color:#fff;border:none;padding:10px 18px;border-radius:12px;font-weight:700;cursor:pointer;font-family:inherit}.btn.primary:disabled{opacity:.65;cursor:not-allowed}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.mh-combo-field{position:relative;z-index:1}.medical-form .mh-combo-field:has(.mh-combo.open){z-index:50}.mh-combo{position:relative;z-index:2}.mh-combo.open{z-index:10}.mh-combo-trigger{width:100%;display:flex;align-items:center;gap:10px;padding:10px 12px;border-radius:12px;border:1px solid var(--border, #e5e7eb);font-size:15px;font-family:inherit;background:var(--surface, #fff);cursor:pointer;text-align:left}.mh-combo-trigger__icon{display:flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:8px;background:var(--surface-2, #f1f5f9);color:var(--muted)}.mh-combo-trigger__icon i{font-size:14px}.mh-combo-trigger__text{flex:1;min-width:0;font-weight:600;color:var(--text)}.mh-combo-chev{font-size:12px;color:var(--muted);transition:transform .15s}.mh-combo.open .mh-combo-chev{transform:rotate(-180deg)}.mh-combo-input{width:100%;padding:10px 12px;border-radius:12px;border:1px solid var(--border, #e5e7eb);font-size:15px;font-family:inherit;background:var(--surface, #fff)}.mh-combo-panel{position:absolute;left:0;right:0;top:calc(100% + 6px);z-index:20;border-radius:12px;border:1px solid var(--border, #e5e7eb);background:var(--surface, #fff);box-shadow:0 12px 28px -12px #0f172a40;overflow:hidden}.mh-combo-search{width:100%;border:none;border-bottom:1px solid var(--border, #e5e7eb);padding:10px 12px;font-size:14px;font-family:inherit;outline:none;background:var(--surface-2, #f8fafc)}.mh-combo-list{list-style:none;margin:0;padding:6px;max-height:220px;overflow-y:auto}.mh-combo-list li{display:flex;align-items:center;gap:8px;padding:9px 10px;border-radius:8px;cursor:pointer;font-size:14px;line-height:1.3}.mh-combo-list li i{color:var(--muted);flex-shrink:0}.mh-combo-list li:hover{background:var(--surface-2, #f1f5f9)}.mh-combo-list li.active{background:color-mix(in srgb,var(--primary, #5b6cff) 14%,white);font-weight:600}.mh-combo-list--places li{font-weight:500}.mh-combo-add{display:flex;align-items:center;gap:8px;width:calc(100% - 12px);margin:6px;padding:10px 12px;border-radius:10px;border:1px dashed var(--border, #cbd5e1);background:color-mix(in srgb,var(--primary, #5b6cff) 8%,white);color:var(--primary, #5b6cff);font-size:13px;font-weight:700;cursor:pointer;font-family:inherit}.mh-combo-add:hover{filter:brightness(.97)}.med-attach-field{margin-top:0}.med-preview{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:8px}.med-preview img{max-width:100%;max-height:120px;border-radius:12px;border:1px solid var(--border, #e5e7eb);object-fit:contain}.med-upload-card{display:flex;align-items:center;gap:10px;width:100%;padding:10px 12px;margin:0;border-radius:14px;border:1px solid var(--border, #e5e7eb);background:linear-gradient(135deg,color-mix(in srgb,var(--primary, #5b6cff) 6%,white),var(--surface, #fff));cursor:pointer;font-family:inherit;box-sizing:border-box;transition:border-color .15s,box-shadow .15s}.med-upload-card:hover{border-color:color-mix(in srgb,var(--primary, #5b6cff) 45%,#e5e7eb);box-shadow:0 6px 20px -12px #5b6cff59}.med-upload-card:active{transform:scale(.99)}.med-upload-card:focus-within{outline:none;border-color:var(--primary, #5b6cff);box-shadow:0 0 0 3px color-mix(in srgb,var(--primary, #5b6cff) 24%,transparent)}.med-upload-card__icon{flex-shrink:0;width:38px;height:38px;display:flex;align-items:center;justify-content:center;border-radius:12px;background:color-mix(in srgb,var(--primary, #5b6cff) 14%,white);color:var(--primary, #5b6cff);font-size:18px}.med-upload-card__text{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px;text-align:left}.med-upload-card__title{font-size:14px;font-weight:800;color:var(--text);line-height:1.25}.med-upload-card__arrow{flex-shrink:0;color:var(--muted);font-size:14px;opacity:.85}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
11562
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: MedicalHistoryDialogComponent, isStandalone: true, selector: "app-medical-history-dialog", inputs: { user: { classPropertyName: "user", publicName: "user", isSignal: true, isRequired: false, transformFunction: null }, explorerEntries: { classPropertyName: "explorerEntries", publicName: "explorerEntries", isSignal: true, isRequired: false, transformFunction: null }, medicalFolderId: { classPropertyName: "medicalFolderId", publicName: "medicalFolderId", isSignal: true, isRequired: false, transformFunction: null }, entries: { classPropertyName: "entries", publicName: "entries", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", saved: "saved" }, host: { listeners: { "document:click": "onDocumentClick($event)" } }, ngImport: i0, template: "@if (dialogOpen()) {\n <div class=\"dialog-overlay\" (click)=\"closeDialog()\">\n <div\n class=\"dialog medical-dialog\"\n role=\"dialog\"\n aria-modal=\"true\"\n (click)=\"$event.stopPropagation()\"\n >\n <div class=\"dialog-head\">\n <div>\n <h2>\n <i class=\"pi pi-pencil\"></i>\n {{ editingEntry() ? 'S\u1EEDa s\u1EF1 ki\u1EC7n' : 'Th\u00EAm s\u1EF1 ki\u1EC7n' }}\n </h2>\n </div>\n <button type=\"button\" class=\"dialog-close\" (click)=\"closeDialog()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n @if (errorMsg()) {\n <div class=\"alert error\">\n <i class=\"pi pi-exclamation-circle\"></i>\n {{ errorMsg() }}\n </div>\n }\n <form class=\"medical-form\" (ngSubmit)=\"submitDialog()\">\n <div class=\"medical-form__body\">\n <label class=\"field\">\n <span class=\"label\">Ng\u00E0y</span>\n <input\n type=\"date\"\n [ngModel]=\"draft().date\"\n (ngModelChange)=\"updateDraft({ date: $event })\"\n name=\"mhDate\"\n [max]=\"maxDateToday\"\n required\n />\n </label>\n <div class=\"field mh-combo-field\">\n <span class=\"label\">Lo\u1EA1i</span>\n <div class=\"mh-combo\" [class.open]=\"kindPickerOpen()\">\n <button\n type=\"button\"\n class=\"mh-combo-trigger\"\n (click)=\"toggleKindPicker(); $event.stopPropagation()\"\n >\n <span class=\"mh-combo-trigger__icon\"\n ><i class=\"fa-solid\" [ngClass]=\"metaFor(draft().kind).icon\"></i\n ></span>\n <span class=\"mh-combo-trigger__text\">{{ metaFor(draft().kind).label }}</span>\n <i class=\"pi pi-chevron-down mh-combo-chev\"></i>\n </button>\n @if (kindPickerOpen()) {\n <div class=\"mh-combo-panel\" (click)=\"$event.stopPropagation()\">\n <input\n type=\"search\"\n class=\"mh-combo-search\"\n placeholder=\"T\u00ECm lo\u1EA1i\u2026\"\n [ngModel]=\"kindSearch()\"\n (ngModelChange)=\"kindSearch.set($event)\"\n name=\"mhKindSearch\"\n autocomplete=\"off\"\n (click)=\"$event.stopPropagation()\"\n />\n <ul class=\"mh-combo-list\" role=\"listbox\">\n @for (km of filteredKindCatalog(); track km) {\n <li\n role=\"option\"\n [class.active]=\"draft().kind === km.id\"\n (click)=\"selectKind(km.id)\"\n >\n <i class=\"fa-solid\" [ngClass]=\"km.icon\"></i> {{ km.label }}\n </li>\n }\n </ul>\n </div>\n }\n </div>\n </div>\n <label class=\"field\">\n <span class=\"label\">Ti\u00EAu \u0111\u1EC1</span>\n <input\n type=\"text\"\n [ngModel]=\"draft().title\"\n (ngModelChange)=\"updateDraft({ title: $event })\"\n name=\"mhTitle\"\n placeholder=\"VD: M\u0169i 2 Pentaxim \u2014 BV Nhi\"\n required\n />\n </label>\n <div class=\"field field--rich\">\n <span class=\"label\">Chi ti\u1EBFt</span>\n <app-rich-text-editor\n [ngModel]=\"draft().detail\"\n (ngModelChange)=\"updateDraft({ detail: $event })\"\n name=\"mhDetail\"\n [minHeight]=\"200\"\n placeholder=\"Ph\u1EA3n \u1EE9ng, ch\u1EC9 \u0111\u1ECBnh BS, li\u1EC1u\u2026\"\n />\n </div>\n <div class=\"field mh-combo-field\">\n <span class=\"label\">N\u01A1i kh\u00E1m</span>\n <div class=\"mh-combo mh-place-combo\" [class.open]=\"placePickerOpen()\">\n <input\n type=\"text\"\n class=\"mh-combo-input\"\n [ngModel]=\"draft().place\"\n (ngModelChange)=\"\n updateDraft({ place: $event });\n kindPickerOpen.set(false);\n placePickerOpen.set(true)\n \"\n (focus)=\"onPlaceFieldFocus()\"\n name=\"mhPlace\"\n placeholder=\"Ch\u1ECDn ho\u1EB7c g\u00F5 t\u00EAn c\u01A1 s\u1EDF\u2026\"\n autocomplete=\"off\"\n />\n @if (placePickerOpen()) {\n <div class=\"mh-combo-panel\" (click)=\"$event.stopPropagation()\">\n @if (canAddCustomPlace()) {\n <button type=\"button\" class=\"mh-combo-add\" (click)=\"addCustomPlaceFromDraft()\">\n <i class=\"pi pi-plus\"></i> Th\u00EAm \u00AB{{ draft().place.trim() }}\u00BB v\u00E0o danh s\u00E1ch\n </button>\n }\n <ul class=\"mh-combo-list mh-combo-list--places\">\n @for (p of filteredPlaceSuggestions(); track p) {\n <li (click)=\"selectPlace(p)\">\n {{ p }}\n </li>\n }\n </ul>\n </div>\n }\n </div>\n </div>\n <div class=\"field med-attach-field\">\n <span class=\"label\">\u1EA2nh \u0111\u00EDnh k\u00E8m</span>\n @if (\n editingEntry() &&\n !pendingAttachmentPreviewUrl() &&\n !stripExistingAttachment() &&\n attachmentFor(editingEntry()!);\n as sav\n ) {\n <div class=\"med-preview med-preview--saved\">\n @if (sav.content || sav.previewUrl) {\n <img [src]=\"sav.previewUrl || sav.content\" alt=\"\u0110\u00EDnh k\u00E8m hi\u1EC7n t\u1EA1i\" />\n }\n <button type=\"button\" class=\"btn ghost sm\" (click)=\"removeSavedAttachment()\">\n <i class=\"pi pi-times\"></i> G\u1EE1 \u1EA3nh \u0111\u00E3 l\u01B0u\n </button>\n </div>\n }\n @if (pendingAttachmentPreviewUrl(); as prevUrl) {\n <div class=\"med-preview med-preview--new\">\n <img [src]=\"prevUrl\" alt=\"Xem tr\u01B0\u1EDBc\" />\n <button type=\"button\" class=\"btn ghost sm\" (click)=\"clearPendingAttachment()\">\n Hu\u1EF7 \u1EA3nh m\u1EDBi\n </button>\n </div>\n }\n <label class=\"med-upload-card\">\n <input\n type=\"file\"\n accept=\"image/*,.heic,.heif\"\n class=\"sr-only\"\n (change)=\"onAttachmentInput($any($event.target))\"\n />\n <span class=\"med-upload-card__icon\" aria-hidden=\"true\">\n <i class=\"pi pi-images\"></i>\n </span>\n <span class=\"med-upload-card__text\">\n <strong class=\"med-upload-card__title\">{{\n pendingAttachment() ? '\u0110\u1ED5i \u1EA3nh' : 'Ch\u1ECDn \u1EA3nh'\n }}</strong>\n </span>\n <span class=\"med-upload-card__arrow\" aria-hidden=\"true\">\n <i class=\"pi pi-angle-right\"></i>\n </span>\n </label>\n </div>\n </div>\n <div class=\"dialog-actions\">\n <button type=\"button\" class=\"btn ghost\" (click)=\"closeDialog()\" [disabled]=\"saving()\">\n Hu\u1EF7\n </button>\n <button type=\"submit\" class=\"btn primary\" [disabled]=\"saving()\">\n @if (!saving()) {\n <i class=\"pi pi-check\"></i>\n }\n @if (saving()) {\n <i class=\"pi pi-spin pi-spinner\"></i>\n }\n {{ editingEntry() ? 'C\u1EADp nh\u1EADt' : 'L\u01B0u' }}\n </button>\n </div>\n </form>\n </div>\n </div>\n}\n", styles: ["@charset \"UTF-8\";.dialog-overlay{position:fixed;inset:0;z-index:250;background:#0f172a73;display:flex;align-items:center;justify-content:center;padding:16px;padding-top:max(16px,env(safe-area-inset-top,0px));padding-bottom:max(16px,env(safe-area-inset-bottom,0px));box-sizing:border-box}@media(max-width:639px){.dialog-overlay{padding-bottom:calc(68px + env(safe-area-inset-bottom,0px));align-items:center}}.medical-dialog{width:100%;max-width:480px;max-height:min(90vh,720px);overflow:hidden;display:flex;flex-direction:column;background:var(--surface, #fff);border-radius:0;padding:0;box-shadow:0 24px 48px -24px #0f172a59}@media(min-width:640px){.medical-dialog{max-height:min(88dvh,720px)}}.medical-dialog .field input,.medical-dialog .field select,.medical-dialog .field textarea,.medical-dialog .mh-combo-trigger,.medical-dialog .mh-combo-input{padding:8px 10px;border-radius:0}.medical-dialog .mh-combo-panel,.medical-dialog .mh-combo-search,.medical-dialog .med-upload-card{border-radius:0}.medical-dialog .alert.error{display:flex;align-items:flex-start;gap:10px;flex-shrink:0;margin:0 16px 8px;padding:10px 12px;border-radius:12px;font-size:13px;background:#fef2f2;color:#b91c1c;border:1px solid #fecaca}.dialog-head{flex-shrink:0;display:flex;justify-content:space-between;align-items:flex-start;gap:12px;padding:14px 16px 10px;border-bottom:1px solid var(--border, #e5e7eb)}.dialog-head h2{margin:0;font-size:1.1rem;font-weight:800;display:flex;align-items:center;gap:8px}.dialog-close{border:none;background:transparent;padding:8px;cursor:pointer;color:var(--muted);border-radius:10px}.dialog-close:hover{background:var(--surface-2, #f1f5f9)}.medical-form{flex:1;min-height:0;display:flex;flex-direction:column;padding:0;gap:0}.medical-form__body{flex:1;min-height:0;overflow-y:auto;overflow-x:hidden;padding:12px 16px;display:flex;flex-direction:column;gap:10px;-webkit-overflow-scrolling:touch}.field{display:flex;flex-direction:column;gap:4px}.field .label{font-size:12px;font-weight:700;color:var(--muted)}.field input,.field select,.field textarea{padding:10px 12px;border-radius:12px;border:1px solid var(--border, #e5e7eb);font-size:15px;font-family:inherit;background:var(--surface, #fff)}.field textarea{resize:vertical;min-height:72px}.field--rich .label{margin-bottom:2px}.dialog-actions{flex-shrink:0;display:flex;justify-content:flex-end;align-items:center;gap:10px;margin:0;padding:12px 16px calc(12px + env(safe-area-inset-bottom,0px));border-top:1px solid var(--border, #e5e7eb);background:var(--surface, #fff)}.btn.ghost{border:1px solid var(--border);background:transparent;padding:10px 18px;border-radius:12px;font-weight:700;cursor:pointer;font-family:inherit}.btn.ghost.sm{padding:6px 12px;font-size:12px;border-radius:10px}.btn.primary{background:var(--primary, #5b6cff);color:#fff;border:none;padding:10px 18px;border-radius:12px;font-weight:700;cursor:pointer;font-family:inherit}.btn.primary:disabled{opacity:.65;cursor:not-allowed}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.mh-combo-field{position:relative;z-index:1}.medical-form .mh-combo-field:has(.mh-combo.open){z-index:50}.mh-combo{position:relative;z-index:2}.mh-combo.open{z-index:10}.mh-combo-trigger{width:100%;display:flex;align-items:center;gap:10px;padding:10px 12px;border-radius:12px;border:1px solid var(--border, #e5e7eb);font-size:15px;font-family:inherit;background:var(--surface, #fff);cursor:pointer;text-align:left}.mh-combo-trigger__icon{display:flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:8px;background:var(--surface-2, #f1f5f9);color:var(--muted)}.mh-combo-trigger__icon i{font-size:14px}.mh-combo-trigger__text{flex:1;min-width:0;font-weight:600;color:var(--text)}.mh-combo-chev{font-size:12px;color:var(--muted);transition:transform .15s}.mh-combo.open .mh-combo-chev{transform:rotate(-180deg)}.mh-combo-input{width:100%;padding:10px 12px;border-radius:12px;border:1px solid var(--border, #e5e7eb);font-size:15px;font-family:inherit;background:var(--surface, #fff)}.mh-combo-panel{position:absolute;left:0;right:0;top:calc(100% + 6px);z-index:20;border-radius:12px;border:1px solid var(--border, #e5e7eb);background:var(--surface, #fff);box-shadow:0 12px 28px -12px #0f172a40;overflow:hidden}.mh-combo-search{width:100%;border:none;border-bottom:1px solid var(--border, #e5e7eb);padding:10px 12px;font-size:14px;font-family:inherit;outline:none;background:var(--surface-2, #f8fafc)}.mh-combo-list{list-style:none;margin:0;padding:6px;max-height:220px;overflow-y:auto}.mh-combo-list li{display:flex;align-items:center;gap:8px;padding:9px 10px;border-radius:8px;cursor:pointer;font-size:14px;line-height:1.3}.mh-combo-list li i{color:var(--muted);flex-shrink:0}.mh-combo-list li:hover{background:var(--surface-2, #f1f5f9)}.mh-combo-list li.active{background:color-mix(in srgb,var(--primary, #5b6cff) 14%,white);font-weight:600}.mh-combo-list--places li{font-weight:500}.mh-combo-add{display:flex;align-items:center;gap:8px;width:calc(100% - 12px);margin:6px;padding:10px 12px;border-radius:10px;border:1px dashed var(--border, #cbd5e1);background:color-mix(in srgb,var(--primary, #5b6cff) 8%,white);color:var(--primary, #5b6cff);font-size:13px;font-weight:700;cursor:pointer;font-family:inherit}.mh-combo-add:hover{filter:brightness(.97)}.med-attach-field{margin-top:0}.med-preview{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:8px}.med-preview img{max-width:100%;max-height:120px;border-radius:12px;border:1px solid var(--border, #e5e7eb);object-fit:contain}.med-upload-card{display:flex;align-items:center;gap:10px;width:100%;padding:10px 12px;margin:0;border-radius:14px;border:1px solid var(--border, #e5e7eb);background:linear-gradient(135deg,color-mix(in srgb,var(--primary, #5b6cff) 6%,white),var(--surface, #fff));cursor:pointer;font-family:inherit;box-sizing:border-box;transition:border-color .15s,box-shadow .15s}.med-upload-card:hover{border-color:color-mix(in srgb,var(--primary, #5b6cff) 45%,#e5e7eb);box-shadow:0 6px 20px -12px #5b6cff59}.med-upload-card:active{transform:scale(.99)}.med-upload-card:focus-within{outline:none;border-color:var(--primary, #5b6cff);box-shadow:0 0 0 3px color-mix(in srgb,var(--primary, #5b6cff) 24%,transparent)}.med-upload-card__icon{flex-shrink:0;width:38px;height:38px;display:flex;align-items:center;justify-content:center;border-radius:12px;background:color-mix(in srgb,var(--primary, #5b6cff) 14%,white);color:var(--primary, #5b6cff);font-size:18px}.med-upload-card__text{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px;text-align:left}.med-upload-card__title{font-size:14px;font-weight:800;color:var(--text);line-height:1.25}.med-upload-card__arrow{flex-shrink:0;color:var(--muted);font-size:14px;opacity:.85}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: RichTextEditorComponent, selector: "app-rich-text-editor", inputs: ["placeholder", "minHeight"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
11388
11563
|
}
|
|
11389
11564
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: MedicalHistoryDialogComponent, decorators: [{
|
|
11390
11565
|
type: Component,
|
|
11391
|
-
args: [{ selector: 'app-medical-history-dialog', standalone: true, imports: [FormsModule, NgClass], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (dialogOpen()) {\n <div class=\"dialog-overlay\" (click)=\"closeDialog()\">\n <div\n class=\"dialog medical-dialog\"\n role=\"dialog\"\n aria-modal=\"true\"\n (click)=\"$event.stopPropagation()\"\n >\n <div class=\"dialog-head\">\n <div>\n <h2>\n <i class=\"pi pi-pencil\"></i>\n {{ editingEntry() ? 'S\u1EEDa s\u1EF1 ki\u1EC7n' : 'Th\u00EAm s\u1EF1 ki\u1EC7n' }}\n </h2>\n </div>\n <button type=\"button\" class=\"dialog-close\" (click)=\"closeDialog()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n @if (errorMsg()) {\n <div class=\"alert error\">\n <i class=\"pi pi-exclamation-circle\"></i>\n {{ errorMsg() }}\n </div>\n }\n <form class=\"medical-form\" (ngSubmit)=\"submitDialog()\">\n <label class=\"field\">\n <span class=\"label\">Ng\u00E0y</span>\n <input\n type=\"date\"\n [ngModel]=\"draft().date\"\n (ngModelChange)=\"updateDraft({ date: $event })\"\n name=\"mhDate\"\n [max]=\"maxDateToday\"\n required\n />\n </label>\n <div class=\"field mh-combo-field\">\n <span class=\"label\">Lo\u1EA1i</span>\n <div class=\"mh-combo\" [class.open]=\"kindPickerOpen()\">\n <button\n type=\"button\"\n class=\"mh-combo-trigger\"\n (click)=\"toggleKindPicker(); $event.stopPropagation()\"\n >\n <span class=\"mh-combo-trigger__icon\"\n ><i class=\"fa-solid\" [ngClass]=\"metaFor(draft().kind).icon\"></i\n ></span>\n <span class=\"mh-combo-trigger__text\">{{ metaFor(draft().kind).label }}</span>\n <i class=\"pi pi-chevron-down mh-combo-chev\"></i>\n </button>\n @if (kindPickerOpen()) {\n <div class=\"mh-combo-panel\" (click)=\"$event.stopPropagation()\">\n <input\n type=\"search\"\n class=\"mh-combo-search\"\n placeholder=\"T\u00ECm lo\u1EA1i\u2026\"\n [ngModel]=\"kindSearch()\"\n (ngModelChange)=\"kindSearch.set($event)\"\n name=\"mhKindSearch\"\n autocomplete=\"off\"\n (click)=\"$event.stopPropagation()\"\n />\n <ul class=\"mh-combo-list\" role=\"listbox\">\n @for (km of filteredKindCatalog(); track km) {\n <li\n role=\"option\"\n [class.active]=\"draft().kind === km.id\"\n (click)=\"selectKind(km.id)\"\n >\n <i class=\"fa-solid\" [ngClass]=\"km.icon\"></i> {{ km.label }}\n </li>\n }\n </ul>\n </div>\n }\n </div>\n </div>\n <label class=\"field\">\n <span class=\"label\">Ti\u00EAu \u0111\u1EC1</span>\n <input\n type=\"text\"\n [ngModel]=\"draft().title\"\n (ngModelChange)=\"updateDraft({ title: $event })\"\n name=\"mhTitle\"\n placeholder=\"VD: M\u0169i 2 Pentaxim \u2014 BV Nhi\"\n required\n />\n </label>\n <label class=\"field\">\n <span class=\"label\">Chi ti\u1EBFt</span>\n <textarea\n [ngModel]=\"draft().detail\"\n (ngModelChange)=\"updateDraft({ detail: $event })\"\n name=\"mhDetail\"\n rows=\"3\"\n placeholder=\"Ph\u1EA3n \u1EE9ng, ch\u1EC9 \u0111\u1ECBnh BS, li\u1EC1u\u2026\"\n ></textarea>\n </label>\n <div class=\"field mh-combo-field\">\n <span class=\"label\">N\u01A1i kh\u00E1m</span>\n <div class=\"mh-combo mh-place-combo\" [class.open]=\"placePickerOpen()\">\n <input\n type=\"text\"\n class=\"mh-combo-input\"\n [ngModel]=\"draft().place\"\n (ngModelChange)=\"\n updateDraft({ place: $event }); kindPickerOpen.set(false); placePickerOpen.set(true)\n \"\n (focus)=\"onPlaceFieldFocus()\"\n name=\"mhPlace\"\n placeholder=\"Ch\u1ECDn ho\u1EB7c g\u00F5 t\u00EAn c\u01A1 s\u1EDF\u2026\"\n autocomplete=\"off\"\n />\n @if (placePickerOpen()) {\n <div class=\"mh-combo-panel\" (click)=\"$event.stopPropagation()\">\n @if (canAddCustomPlace()) {\n <button type=\"button\" class=\"mh-combo-add\" (click)=\"addCustomPlaceFromDraft()\">\n <i class=\"pi pi-plus\"></i> Th\u00EAm \u00AB{{ draft().place.trim() }}\u00BB v\u00E0o danh s\u00E1ch\n </button>\n }\n <ul class=\"mh-combo-list mh-combo-list--places\">\n @for (p of filteredPlaceSuggestions(); track p) {\n <li (click)=\"selectPlace(p)\">\n {{ p }}\n </li>\n }\n </ul>\n </div>\n }\n </div>\n </div>\n <div class=\"field med-attach-field\">\n <span class=\"label\">\u1EA2nh \u0111\u00EDnh k\u00E8m</span>\n @if (\n editingEntry() &&\n !pendingAttachmentPreviewUrl() &&\n !stripExistingAttachment() &&\n attachmentFor(editingEntry()!);\n as sav\n ) {\n <div class=\"med-preview med-preview--saved\">\n @if (sav.content || sav.previewUrl) {\n <img [src]=\"sav.previewUrl || sav.content\" alt=\"\u0110\u00EDnh k\u00E8m hi\u1EC7n t\u1EA1i\" />\n }\n <button type=\"button\" class=\"btn ghost sm\" (click)=\"removeSavedAttachment()\">\n <i class=\"pi pi-times\"></i> G\u1EE1 \u1EA3nh \u0111\u00E3 l\u01B0u\n </button>\n </div>\n }\n @if (pendingAttachmentPreviewUrl(); as prevUrl) {\n <div class=\"med-preview med-preview--new\">\n <img [src]=\"prevUrl\" alt=\"Xem tr\u01B0\u1EDBc\" />\n <button type=\"button\" class=\"btn ghost sm\" (click)=\"clearPendingAttachment()\">\n Hu\u1EF7 \u1EA3nh m\u1EDBi\n </button>\n </div>\n }\n <label class=\"med-upload-card\">\n <input\n type=\"file\"\n accept=\"image/*,.heic,.heif\"\n class=\"sr-only\"\n (change)=\"onAttachmentInput($any($event.target))\"\n />\n <span class=\"med-upload-card__icon\" aria-hidden=\"true\">\n <i class=\"pi pi-images\"></i>\n </span>\n <span class=\"med-upload-card__text\">\n <strong class=\"med-upload-card__title\">{{\n pendingAttachment() ? '\u0110\u1ED5i \u1EA3nh' : 'Ch\u1ECDn \u1EA3nh'\n }}</strong>\n </span>\n <span class=\"med-upload-card__arrow\" aria-hidden=\"true\">\n <i class=\"pi pi-angle-right\"></i>\n </span>\n </label>\n </div>\n <div class=\"dialog-actions\">\n <button type=\"button\" class=\"btn ghost\" (click)=\"closeDialog()\" [disabled]=\"saving()\">\n Hu\u1EF7\n </button>\n <button type=\"submit\" class=\"btn primary\" [disabled]=\"saving()\">\n @if (!saving()) {\n <i class=\"pi pi-check\"></i>\n }\n @if (saving()) {\n <i class=\"pi pi-spin pi-spinner\"></i>\n }\n {{ editingEntry() ? 'C\u1EADp nh\u1EADt' : 'L\u01B0u' }}\n </button>\n </div>\n </form>\n </div>\n </div>\n}\n", styles: ["@charset \"UTF-8\";.dialog-overlay{position:fixed;inset:0;z-index:250;background:#0f172a73;display:flex;align-items:center;justify-content:center;padding:16px;padding-top:max(16px,env(safe-area-inset-top,0px));padding-bottom:max(16px,env(safe-area-inset-bottom,0px));box-sizing:border-box}@media(max-width:639px){.dialog-overlay{padding-bottom:calc(68px + env(safe-area-inset-bottom,0px));align-items:center}}.medical-dialog{width:100%;max-width:480px;max-height:90vh;overflow:auto;background:var(--surface, #fff);border-radius:0;padding:0 0 12px;box-shadow:0 24px 48px -24px #0f172a59}@media(min-width:640px){.medical-dialog{max-height:min(88dvh,720px);padding-bottom:env(safe-area-inset-bottom,12px)}}.medical-dialog .field input,.medical-dialog .field select,.medical-dialog .field textarea,.medical-dialog .mh-combo-trigger,.medical-dialog .mh-combo-input{padding:8px 10px;border-radius:0}.medical-dialog .mh-combo-panel,.medical-dialog .mh-combo-search,.medical-dialog .med-upload-card{border-radius:0}.medical-dialog .alert.error{display:flex;align-items:flex-start;gap:10px;margin:0 16px 8px;padding:10px 12px;border-radius:12px;font-size:13px;background:#fef2f2;color:#b91c1c;border:1px solid #fecaca}.dialog-head{display:flex;justify-content:space-between;align-items:flex-start;gap:12px;padding:14px 16px 10px;border-bottom:1px solid var(--border, #e5e7eb)}.dialog-head h2{margin:0;font-size:1.1rem;font-weight:800;display:flex;align-items:center;gap:8px}.dialog-close{border:none;background:transparent;padding:8px;cursor:pointer;color:var(--muted);border-radius:10px}.dialog-close:hover{background:var(--surface-2, #f1f5f9)}.medical-form{padding:12px 16px 16px;display:flex;flex-direction:column;gap:10px}.field{display:flex;flex-direction:column;gap:4px}.field .label{font-size:12px;font-weight:700;color:var(--muted)}.field input,.field select,.field textarea{padding:10px 12px;border-radius:12px;border:1px solid var(--border, #e5e7eb);font-size:15px;font-family:inherit;background:var(--surface, #fff)}.field textarea{resize:vertical;min-height:72px}.dialog-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:4px;padding-top:4px}.btn.ghost{border:1px solid var(--border);background:transparent;padding:10px 18px;border-radius:12px;font-weight:700;cursor:pointer;font-family:inherit}.btn.ghost.sm{padding:6px 12px;font-size:12px;border-radius:10px}.btn.primary{background:var(--primary, #5b6cff);color:#fff;border:none;padding:10px 18px;border-radius:12px;font-weight:700;cursor:pointer;font-family:inherit}.btn.primary:disabled{opacity:.65;cursor:not-allowed}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.mh-combo-field{position:relative;z-index:1}.medical-form .mh-combo-field:has(.mh-combo.open){z-index:50}.mh-combo{position:relative;z-index:2}.mh-combo.open{z-index:10}.mh-combo-trigger{width:100%;display:flex;align-items:center;gap:10px;padding:10px 12px;border-radius:12px;border:1px solid var(--border, #e5e7eb);font-size:15px;font-family:inherit;background:var(--surface, #fff);cursor:pointer;text-align:left}.mh-combo-trigger__icon{display:flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:8px;background:var(--surface-2, #f1f5f9);color:var(--muted)}.mh-combo-trigger__icon i{font-size:14px}.mh-combo-trigger__text{flex:1;min-width:0;font-weight:600;color:var(--text)}.mh-combo-chev{font-size:12px;color:var(--muted);transition:transform .15s}.mh-combo.open .mh-combo-chev{transform:rotate(-180deg)}.mh-combo-input{width:100%;padding:10px 12px;border-radius:12px;border:1px solid var(--border, #e5e7eb);font-size:15px;font-family:inherit;background:var(--surface, #fff)}.mh-combo-panel{position:absolute;left:0;right:0;top:calc(100% + 6px);z-index:20;border-radius:12px;border:1px solid var(--border, #e5e7eb);background:var(--surface, #fff);box-shadow:0 12px 28px -12px #0f172a40;overflow:hidden}.mh-combo-search{width:100%;border:none;border-bottom:1px solid var(--border, #e5e7eb);padding:10px 12px;font-size:14px;font-family:inherit;outline:none;background:var(--surface-2, #f8fafc)}.mh-combo-list{list-style:none;margin:0;padding:6px;max-height:220px;overflow-y:auto}.mh-combo-list li{display:flex;align-items:center;gap:8px;padding:9px 10px;border-radius:8px;cursor:pointer;font-size:14px;line-height:1.3}.mh-combo-list li i{color:var(--muted);flex-shrink:0}.mh-combo-list li:hover{background:var(--surface-2, #f1f5f9)}.mh-combo-list li.active{background:color-mix(in srgb,var(--primary, #5b6cff) 14%,white);font-weight:600}.mh-combo-list--places li{font-weight:500}.mh-combo-add{display:flex;align-items:center;gap:8px;width:calc(100% - 12px);margin:6px;padding:10px 12px;border-radius:10px;border:1px dashed var(--border, #cbd5e1);background:color-mix(in srgb,var(--primary, #5b6cff) 8%,white);color:var(--primary, #5b6cff);font-size:13px;font-weight:700;cursor:pointer;font-family:inherit}.mh-combo-add:hover{filter:brightness(.97)}.med-attach-field{margin-top:0}.med-preview{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:8px}.med-preview img{max-width:100%;max-height:120px;border-radius:12px;border:1px solid var(--border, #e5e7eb);object-fit:contain}.med-upload-card{display:flex;align-items:center;gap:10px;width:100%;padding:10px 12px;margin:0;border-radius:14px;border:1px solid var(--border, #e5e7eb);background:linear-gradient(135deg,color-mix(in srgb,var(--primary, #5b6cff) 6%,white),var(--surface, #fff));cursor:pointer;font-family:inherit;box-sizing:border-box;transition:border-color .15s,box-shadow .15s}.med-upload-card:hover{border-color:color-mix(in srgb,var(--primary, #5b6cff) 45%,#e5e7eb);box-shadow:0 6px 20px -12px #5b6cff59}.med-upload-card:active{transform:scale(.99)}.med-upload-card:focus-within{outline:none;border-color:var(--primary, #5b6cff);box-shadow:0 0 0 3px color-mix(in srgb,var(--primary, #5b6cff) 24%,transparent)}.med-upload-card__icon{flex-shrink:0;width:38px;height:38px;display:flex;align-items:center;justify-content:center;border-radius:12px;background:color-mix(in srgb,var(--primary, #5b6cff) 14%,white);color:var(--primary, #5b6cff);font-size:18px}.med-upload-card__text{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px;text-align:left}.med-upload-card__title{font-size:14px;font-weight:800;color:var(--text);line-height:1.25}.med-upload-card__arrow{flex-shrink:0;color:var(--muted);font-size:14px;opacity:.85}\n"] }]
|
|
11566
|
+
args: [{ selector: 'app-medical-history-dialog', standalone: true, imports: [FormsModule, NgClass, RichTextEditorComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (dialogOpen()) {\n <div class=\"dialog-overlay\" (click)=\"closeDialog()\">\n <div\n class=\"dialog medical-dialog\"\n role=\"dialog\"\n aria-modal=\"true\"\n (click)=\"$event.stopPropagation()\"\n >\n <div class=\"dialog-head\">\n <div>\n <h2>\n <i class=\"pi pi-pencil\"></i>\n {{ editingEntry() ? 'S\u1EEDa s\u1EF1 ki\u1EC7n' : 'Th\u00EAm s\u1EF1 ki\u1EC7n' }}\n </h2>\n </div>\n <button type=\"button\" class=\"dialog-close\" (click)=\"closeDialog()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n @if (errorMsg()) {\n <div class=\"alert error\">\n <i class=\"pi pi-exclamation-circle\"></i>\n {{ errorMsg() }}\n </div>\n }\n <form class=\"medical-form\" (ngSubmit)=\"submitDialog()\">\n <div class=\"medical-form__body\">\n <label class=\"field\">\n <span class=\"label\">Ng\u00E0y</span>\n <input\n type=\"date\"\n [ngModel]=\"draft().date\"\n (ngModelChange)=\"updateDraft({ date: $event })\"\n name=\"mhDate\"\n [max]=\"maxDateToday\"\n required\n />\n </label>\n <div class=\"field mh-combo-field\">\n <span class=\"label\">Lo\u1EA1i</span>\n <div class=\"mh-combo\" [class.open]=\"kindPickerOpen()\">\n <button\n type=\"button\"\n class=\"mh-combo-trigger\"\n (click)=\"toggleKindPicker(); $event.stopPropagation()\"\n >\n <span class=\"mh-combo-trigger__icon\"\n ><i class=\"fa-solid\" [ngClass]=\"metaFor(draft().kind).icon\"></i\n ></span>\n <span class=\"mh-combo-trigger__text\">{{ metaFor(draft().kind).label }}</span>\n <i class=\"pi pi-chevron-down mh-combo-chev\"></i>\n </button>\n @if (kindPickerOpen()) {\n <div class=\"mh-combo-panel\" (click)=\"$event.stopPropagation()\">\n <input\n type=\"search\"\n class=\"mh-combo-search\"\n placeholder=\"T\u00ECm lo\u1EA1i\u2026\"\n [ngModel]=\"kindSearch()\"\n (ngModelChange)=\"kindSearch.set($event)\"\n name=\"mhKindSearch\"\n autocomplete=\"off\"\n (click)=\"$event.stopPropagation()\"\n />\n <ul class=\"mh-combo-list\" role=\"listbox\">\n @for (km of filteredKindCatalog(); track km) {\n <li\n role=\"option\"\n [class.active]=\"draft().kind === km.id\"\n (click)=\"selectKind(km.id)\"\n >\n <i class=\"fa-solid\" [ngClass]=\"km.icon\"></i> {{ km.label }}\n </li>\n }\n </ul>\n </div>\n }\n </div>\n </div>\n <label class=\"field\">\n <span class=\"label\">Ti\u00EAu \u0111\u1EC1</span>\n <input\n type=\"text\"\n [ngModel]=\"draft().title\"\n (ngModelChange)=\"updateDraft({ title: $event })\"\n name=\"mhTitle\"\n placeholder=\"VD: M\u0169i 2 Pentaxim \u2014 BV Nhi\"\n required\n />\n </label>\n <div class=\"field field--rich\">\n <span class=\"label\">Chi ti\u1EBFt</span>\n <app-rich-text-editor\n [ngModel]=\"draft().detail\"\n (ngModelChange)=\"updateDraft({ detail: $event })\"\n name=\"mhDetail\"\n [minHeight]=\"200\"\n placeholder=\"Ph\u1EA3n \u1EE9ng, ch\u1EC9 \u0111\u1ECBnh BS, li\u1EC1u\u2026\"\n />\n </div>\n <div class=\"field mh-combo-field\">\n <span class=\"label\">N\u01A1i kh\u00E1m</span>\n <div class=\"mh-combo mh-place-combo\" [class.open]=\"placePickerOpen()\">\n <input\n type=\"text\"\n class=\"mh-combo-input\"\n [ngModel]=\"draft().place\"\n (ngModelChange)=\"\n updateDraft({ place: $event });\n kindPickerOpen.set(false);\n placePickerOpen.set(true)\n \"\n (focus)=\"onPlaceFieldFocus()\"\n name=\"mhPlace\"\n placeholder=\"Ch\u1ECDn ho\u1EB7c g\u00F5 t\u00EAn c\u01A1 s\u1EDF\u2026\"\n autocomplete=\"off\"\n />\n @if (placePickerOpen()) {\n <div class=\"mh-combo-panel\" (click)=\"$event.stopPropagation()\">\n @if (canAddCustomPlace()) {\n <button type=\"button\" class=\"mh-combo-add\" (click)=\"addCustomPlaceFromDraft()\">\n <i class=\"pi pi-plus\"></i> Th\u00EAm \u00AB{{ draft().place.trim() }}\u00BB v\u00E0o danh s\u00E1ch\n </button>\n }\n <ul class=\"mh-combo-list mh-combo-list--places\">\n @for (p of filteredPlaceSuggestions(); track p) {\n <li (click)=\"selectPlace(p)\">\n {{ p }}\n </li>\n }\n </ul>\n </div>\n }\n </div>\n </div>\n <div class=\"field med-attach-field\">\n <span class=\"label\">\u1EA2nh \u0111\u00EDnh k\u00E8m</span>\n @if (\n editingEntry() &&\n !pendingAttachmentPreviewUrl() &&\n !stripExistingAttachment() &&\n attachmentFor(editingEntry()!);\n as sav\n ) {\n <div class=\"med-preview med-preview--saved\">\n @if (sav.content || sav.previewUrl) {\n <img [src]=\"sav.previewUrl || sav.content\" alt=\"\u0110\u00EDnh k\u00E8m hi\u1EC7n t\u1EA1i\" />\n }\n <button type=\"button\" class=\"btn ghost sm\" (click)=\"removeSavedAttachment()\">\n <i class=\"pi pi-times\"></i> G\u1EE1 \u1EA3nh \u0111\u00E3 l\u01B0u\n </button>\n </div>\n }\n @if (pendingAttachmentPreviewUrl(); as prevUrl) {\n <div class=\"med-preview med-preview--new\">\n <img [src]=\"prevUrl\" alt=\"Xem tr\u01B0\u1EDBc\" />\n <button type=\"button\" class=\"btn ghost sm\" (click)=\"clearPendingAttachment()\">\n Hu\u1EF7 \u1EA3nh m\u1EDBi\n </button>\n </div>\n }\n <label class=\"med-upload-card\">\n <input\n type=\"file\"\n accept=\"image/*,.heic,.heif\"\n class=\"sr-only\"\n (change)=\"onAttachmentInput($any($event.target))\"\n />\n <span class=\"med-upload-card__icon\" aria-hidden=\"true\">\n <i class=\"pi pi-images\"></i>\n </span>\n <span class=\"med-upload-card__text\">\n <strong class=\"med-upload-card__title\">{{\n pendingAttachment() ? '\u0110\u1ED5i \u1EA3nh' : 'Ch\u1ECDn \u1EA3nh'\n }}</strong>\n </span>\n <span class=\"med-upload-card__arrow\" aria-hidden=\"true\">\n <i class=\"pi pi-angle-right\"></i>\n </span>\n </label>\n </div>\n </div>\n <div class=\"dialog-actions\">\n <button type=\"button\" class=\"btn ghost\" (click)=\"closeDialog()\" [disabled]=\"saving()\">\n Hu\u1EF7\n </button>\n <button type=\"submit\" class=\"btn primary\" [disabled]=\"saving()\">\n @if (!saving()) {\n <i class=\"pi pi-check\"></i>\n }\n @if (saving()) {\n <i class=\"pi pi-spin pi-spinner\"></i>\n }\n {{ editingEntry() ? 'C\u1EADp nh\u1EADt' : 'L\u01B0u' }}\n </button>\n </div>\n </form>\n </div>\n </div>\n}\n", styles: ["@charset \"UTF-8\";.dialog-overlay{position:fixed;inset:0;z-index:250;background:#0f172a73;display:flex;align-items:center;justify-content:center;padding:16px;padding-top:max(16px,env(safe-area-inset-top,0px));padding-bottom:max(16px,env(safe-area-inset-bottom,0px));box-sizing:border-box}@media(max-width:639px){.dialog-overlay{padding-bottom:calc(68px + env(safe-area-inset-bottom,0px));align-items:center}}.medical-dialog{width:100%;max-width:480px;max-height:min(90vh,720px);overflow:hidden;display:flex;flex-direction:column;background:var(--surface, #fff);border-radius:0;padding:0;box-shadow:0 24px 48px -24px #0f172a59}@media(min-width:640px){.medical-dialog{max-height:min(88dvh,720px)}}.medical-dialog .field input,.medical-dialog .field select,.medical-dialog .field textarea,.medical-dialog .mh-combo-trigger,.medical-dialog .mh-combo-input{padding:8px 10px;border-radius:0}.medical-dialog .mh-combo-panel,.medical-dialog .mh-combo-search,.medical-dialog .med-upload-card{border-radius:0}.medical-dialog .alert.error{display:flex;align-items:flex-start;gap:10px;flex-shrink:0;margin:0 16px 8px;padding:10px 12px;border-radius:12px;font-size:13px;background:#fef2f2;color:#b91c1c;border:1px solid #fecaca}.dialog-head{flex-shrink:0;display:flex;justify-content:space-between;align-items:flex-start;gap:12px;padding:14px 16px 10px;border-bottom:1px solid var(--border, #e5e7eb)}.dialog-head h2{margin:0;font-size:1.1rem;font-weight:800;display:flex;align-items:center;gap:8px}.dialog-close{border:none;background:transparent;padding:8px;cursor:pointer;color:var(--muted);border-radius:10px}.dialog-close:hover{background:var(--surface-2, #f1f5f9)}.medical-form{flex:1;min-height:0;display:flex;flex-direction:column;padding:0;gap:0}.medical-form__body{flex:1;min-height:0;overflow-y:auto;overflow-x:hidden;padding:12px 16px;display:flex;flex-direction:column;gap:10px;-webkit-overflow-scrolling:touch}.field{display:flex;flex-direction:column;gap:4px}.field .label{font-size:12px;font-weight:700;color:var(--muted)}.field input,.field select,.field textarea{padding:10px 12px;border-radius:12px;border:1px solid var(--border, #e5e7eb);font-size:15px;font-family:inherit;background:var(--surface, #fff)}.field textarea{resize:vertical;min-height:72px}.field--rich .label{margin-bottom:2px}.dialog-actions{flex-shrink:0;display:flex;justify-content:flex-end;align-items:center;gap:10px;margin:0;padding:12px 16px calc(12px + env(safe-area-inset-bottom,0px));border-top:1px solid var(--border, #e5e7eb);background:var(--surface, #fff)}.btn.ghost{border:1px solid var(--border);background:transparent;padding:10px 18px;border-radius:12px;font-weight:700;cursor:pointer;font-family:inherit}.btn.ghost.sm{padding:6px 12px;font-size:12px;border-radius:10px}.btn.primary{background:var(--primary, #5b6cff);color:#fff;border:none;padding:10px 18px;border-radius:12px;font-weight:700;cursor:pointer;font-family:inherit}.btn.primary:disabled{opacity:.65;cursor:not-allowed}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.mh-combo-field{position:relative;z-index:1}.medical-form .mh-combo-field:has(.mh-combo.open){z-index:50}.mh-combo{position:relative;z-index:2}.mh-combo.open{z-index:10}.mh-combo-trigger{width:100%;display:flex;align-items:center;gap:10px;padding:10px 12px;border-radius:12px;border:1px solid var(--border, #e5e7eb);font-size:15px;font-family:inherit;background:var(--surface, #fff);cursor:pointer;text-align:left}.mh-combo-trigger__icon{display:flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:8px;background:var(--surface-2, #f1f5f9);color:var(--muted)}.mh-combo-trigger__icon i{font-size:14px}.mh-combo-trigger__text{flex:1;min-width:0;font-weight:600;color:var(--text)}.mh-combo-chev{font-size:12px;color:var(--muted);transition:transform .15s}.mh-combo.open .mh-combo-chev{transform:rotate(-180deg)}.mh-combo-input{width:100%;padding:10px 12px;border-radius:12px;border:1px solid var(--border, #e5e7eb);font-size:15px;font-family:inherit;background:var(--surface, #fff)}.mh-combo-panel{position:absolute;left:0;right:0;top:calc(100% + 6px);z-index:20;border-radius:12px;border:1px solid var(--border, #e5e7eb);background:var(--surface, #fff);box-shadow:0 12px 28px -12px #0f172a40;overflow:hidden}.mh-combo-search{width:100%;border:none;border-bottom:1px solid var(--border, #e5e7eb);padding:10px 12px;font-size:14px;font-family:inherit;outline:none;background:var(--surface-2, #f8fafc)}.mh-combo-list{list-style:none;margin:0;padding:6px;max-height:220px;overflow-y:auto}.mh-combo-list li{display:flex;align-items:center;gap:8px;padding:9px 10px;border-radius:8px;cursor:pointer;font-size:14px;line-height:1.3}.mh-combo-list li i{color:var(--muted);flex-shrink:0}.mh-combo-list li:hover{background:var(--surface-2, #f1f5f9)}.mh-combo-list li.active{background:color-mix(in srgb,var(--primary, #5b6cff) 14%,white);font-weight:600}.mh-combo-list--places li{font-weight:500}.mh-combo-add{display:flex;align-items:center;gap:8px;width:calc(100% - 12px);margin:6px;padding:10px 12px;border-radius:10px;border:1px dashed var(--border, #cbd5e1);background:color-mix(in srgb,var(--primary, #5b6cff) 8%,white);color:var(--primary, #5b6cff);font-size:13px;font-weight:700;cursor:pointer;font-family:inherit}.mh-combo-add:hover{filter:brightness(.97)}.med-attach-field{margin-top:0}.med-preview{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:8px}.med-preview img{max-width:100%;max-height:120px;border-radius:12px;border:1px solid var(--border, #e5e7eb);object-fit:contain}.med-upload-card{display:flex;align-items:center;gap:10px;width:100%;padding:10px 12px;margin:0;border-radius:14px;border:1px solid var(--border, #e5e7eb);background:linear-gradient(135deg,color-mix(in srgb,var(--primary, #5b6cff) 6%,white),var(--surface, #fff));cursor:pointer;font-family:inherit;box-sizing:border-box;transition:border-color .15s,box-shadow .15s}.med-upload-card:hover{border-color:color-mix(in srgb,var(--primary, #5b6cff) 45%,#e5e7eb);box-shadow:0 6px 20px -12px #5b6cff59}.med-upload-card:active{transform:scale(.99)}.med-upload-card:focus-within{outline:none;border-color:var(--primary, #5b6cff);box-shadow:0 0 0 3px color-mix(in srgb,var(--primary, #5b6cff) 24%,transparent)}.med-upload-card__icon{flex-shrink:0;width:38px;height:38px;display:flex;align-items:center;justify-content:center;border-radius:12px;background:color-mix(in srgb,var(--primary, #5b6cff) 14%,white);color:var(--primary, #5b6cff);font-size:18px}.med-upload-card__text{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px;text-align:left}.med-upload-card__title{font-size:14px;font-weight:800;color:var(--text);line-height:1.25}.med-upload-card__arrow{flex-shrink:0;color:var(--muted);font-size:14px;opacity:.85}\n"] }]
|
|
11392
11567
|
}], ctorParameters: () => [], propDecorators: { user: [{ type: i0.Input, args: [{ isSignal: true, alias: "user", required: false }] }], explorerEntries: [{ type: i0.Input, args: [{ isSignal: true, alias: "explorerEntries", required: false }] }], medicalFolderId: [{ type: i0.Input, args: [{ isSignal: true, alias: "medicalFolderId", required: false }] }], entries: [{ type: i0.Input, args: [{ isSignal: true, alias: "entries", required: false }] }], closed: [{
|
|
11393
11568
|
type: Output
|
|
11394
11569
|
}], saved: [{
|
|
@@ -11446,6 +11621,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
11446
11621
|
args: [{ selector: 'app-medical-history-filter', standalone: true, imports: [FormsModule, NgClass], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (!loading() && myEntriesCount() > 0) {\n <div class=\"mh-toolbar card\">\n <div class=\"mh-toolbar__search-row\">\n <label class=\"mh-toolbar__search-label\" for=\"mhFreeTextSearch\">\n <i class=\"pi pi-search\" aria-hidden=\"true\"></i>\n T\u00ECm trong ti\u1EC1n s\u1EED\n </label>\n <div class=\"mh-toolbar__search\">\n <input\n id=\"mhFreeTextSearch\"\n type=\"search\"\n class=\"mh-toolbar__search-input\"\n placeholder=\"Ti\u00EAu \u0111\u1EC1, chi ti\u1EBFt, n\u01A1i kh\u00E1m\u2026\"\n [ngModel]=\"textSearchQuery()\"\n (ngModelChange)=\"onTextSearchChange($event)\"\n name=\"mhFreeTextSearch\"\n autocomplete=\"off\"\n enterkeyhint=\"search\"\n />\n @if (textSearchQuery().trim()) {\n <button\n type=\"button\"\n class=\"mh-toolbar__search-clear btn ghost sm\"\n (click)=\"clearTextSearch.emit()\"\n aria-label=\"Xo\u00E1 \u00F4 t\u00ECm\"\n title=\"Xo\u00E1 \u00F4 t\u00ECm\"\n >\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\n </button>\n }\n </div>\n </div>\n\n <div class=\"mh-toolbar__pills\" role=\"toolbar\" aria-label=\"L\u1ECDc theo lo\u1EA1i s\u1EF1 ki\u1EC7n\">\n <button\n type=\"button\"\n class=\"mh-kind-pill\"\n [class.active]=\"kindFilterKinds().length === 0\"\n (click)=\"clearKindFilters.emit()\"\n >\n T\u1EA5t c\u1EA3\n <span class=\"mh-kind-pill__count\">{{ myEntriesCount() }}</span>\n </button>\n @for (km of quickKinds(); track km.id) {\n <button\n type=\"button\"\n class=\"mh-kind-pill\"\n [class.active]=\"kindFilterKindSet().has(km.id)\"\n [style.--chip-color]=\"km.color\"\n (click)=\"toggleKindFilter.emit(km.id)\"\n [attr.title]=\"km.label\"\n >\n <i class=\"fa-solid\" [ngClass]=\"km.icon\" aria-hidden=\"true\"></i>\n {{ km.shortLabel }}\n <span class=\"mh-kind-pill__count\">{{ kindCounts()[km.id] }}</span>\n </button>\n }\n <button\n type=\"button\"\n class=\"mh-kind-pill mh-kind-pill--more\"\n [class.active]=\"filterPanelOpen()\"\n (click)=\"toggleFilterPanel.emit(); $event.stopPropagation()\"\n [attr.aria-expanded]=\"filterPanelOpen()\"\n aria-controls=\"medical-filter-panel\"\n >\n <i class=\"pi pi-sliders-h\" aria-hidden=\"true\"></i>\n Th\u00EAm\n </button>\n </div>\n\n <div class=\"mh-toolbar__meta\">\n <span class=\"mh-toolbar__count\">{{ visibleCount() }} hi\u1EC3n th\u1ECB</span>\n <button\n type=\"button\"\n class=\"mh-toolbar__sort\"\n (click)=\"onTimelineSort(timelineSort() === 'newest' ? 'oldest' : 'newest')\"\n [attr.title]=\"timelineSort() === 'newest' ? '\u0110ang x\u1EBFp m\u1EDBi nh\u1EA5t tr\u01B0\u1EDBc' : '\u0110ang x\u1EBFp c\u0169 nh\u1EA5t tr\u01B0\u1EDBc'\"\n >\n <i class=\"pi pi-sort-alt\" aria-hidden=\"true\"></i>\n {{ timelineSort() === 'newest' ? 'M\u1EDBi nh\u1EA5t' : 'C\u0169 nh\u1EA5t' }}\n </button>\n </div>\n </div>\n}\n\n<div class=\"mh-filter\" [class.mh-filter--open]=\"filterPanelOpen()\">\n @if (filterPanelOpen()) {\n <div\n class=\"mh-filter-panel card\"\n id=\"medical-filter-panel\"\n role=\"region\"\n aria-label=\"L\u1ECDc n\u00E2ng cao\"\n (click)=\"$event.stopPropagation()\"\n >\n <div class=\"mh-filter-panel__headrow\">\n <div class=\"mh-filter-panel__title\">L\u1ECDc & s\u1EAFp x\u1EBFp</div>\n <button\n type=\"button\"\n class=\"mh-filter-panel__reset btn ghost sm\"\n (click)=\"resetFilterPanel.emit()\"\n title=\"\u0110\u1EB7t l\u1EA1i b\u1ED9 l\u1ECDc\"\n >\n <i class=\"pi pi-replay\" aria-hidden=\"true\"></i>\n \u0110\u1EB7t l\u1EA1i\n </button>\n </div>\n <div class=\"mh-filter-panel__field\">\n <span class=\"mh-filter-panel__label\">Th\u1EDDi gian hi\u1EC3n th\u1ECB</span>\n <div class=\"mh-filter-panel__seg\" role=\"group\" aria-label=\"S\u1EAFp x\u1EBFp timeline\">\n <button\n type=\"button\"\n [class.active]=\"timelineSort() === 'newest'\"\n (click)=\"onTimelineSort('newest')\"\n >\n M\u1EDBi nh\u1EA5t tr\u01B0\u1EDBc\n </button>\n <button\n type=\"button\"\n [class.active]=\"timelineSort() === 'oldest'\"\n (click)=\"onTimelineSort('oldest')\"\n >\n C\u0169 nh\u1EA5t tr\u01B0\u1EDBc\n </button>\n </div>\n </div>\n <div class=\"mh-filter-panel__field\">\n <label class=\"mh-filter-panel__label\" for=\"mhFilterKindSearch\">T\u00ECm lo\u1EA1i</label>\n <input\n id=\"mhFilterKindSearch\"\n type=\"search\"\n class=\"mh-filter-panel__search\"\n placeholder=\"VD: tiem, kham, o / \u00F4\u2026\"\n [ngModel]=\"filterPanelKindSearch()\"\n (ngModelChange)=\"onFilterPanelKindSearchChange($event)\"\n name=\"mhFilterKindSearch\"\n autocomplete=\"off\"\n />\n </div>\n <div class=\"mh-filter-panel__field\">\n <span class=\"mh-filter-panel__label\">S\u1EAFp danh s\u00E1ch lo\u1EA1i</span>\n <div class=\"mh-filter-panel__seg\" role=\"group\" aria-label=\"S\u1EAFp lo\u1EA1i\">\n <button\n type=\"button\"\n [class.active]=\"kindListSort() === 'count'\"\n (click)=\"onKindListSort('count')\"\n >\n Nhi\u1EC1u s\u1EF1 ki\u1EC7n tr\u01B0\u1EDBc\n </button>\n <button\n type=\"button\"\n [class.active]=\"kindListSort() === 'alpha'\"\n (click)=\"onKindListSort('alpha')\"\n >\n A \u2192 Z\n </button>\n </div>\n </div>\n <div\n class=\"mh-filter-panel__kinds\"\n role=\"listbox\"\n aria-label=\"Ch\u1ECDn lo\u1EA1i s\u1EF1 ki\u1EC7n\"\n aria-multiselectable=\"true\"\n >\n <button\n type=\"button\"\n class=\"kind-chip mh-filter-kind\"\n [class.active]=\"kindFilterKinds().length === 0\"\n (click)=\"clearKindFilters.emit()\"\n role=\"option\"\n [attr.aria-selected]=\"kindFilterKinds().length === 0\"\n >\n T\u1EA5t c\u1EA3\n <span class=\"kind-chip__count\">{{ myEntriesCount() }}</span>\n </button>\n @for (km of kindsInFilterPanel(); track km.id) {\n <button\n type=\"button\"\n class=\"kind-chip mh-filter-kind\"\n [class.active]=\"kindFilterKindSet().has(km.id)\"\n [style.--chip-color]=\"km.color\"\n (click)=\"toggleKindFilter.emit(km.id)\"\n [attr.title]=\"km.label\"\n role=\"option\"\n [attr.aria-selected]=\"kindFilterKindSet().has(km.id)\"\n >\n <i class=\"fa-solid\" [ngClass]=\"km.icon\" aria-hidden=\"true\"></i>\n <span class=\"mh-filter-kind__name\">{{ km.shortLabel }}</span>\n <span class=\"kind-chip__count\">{{ kindCounts()[km.id] }}</span>\n </button>\n }\n @if (filterPanelKindSearch().trim() && kindsInFilterPanel().length === 0) {\n <p class=\"mh-filter-panel__empty\">\n Kh\u00F4ng th\u1EA5y lo\u1EA1i n\u00E0o \u2014 th\u1EED t\u1EEB ng\u1EAFn h\u01A1n (o/\u00F4, a/\u0103 t\u01B0\u01A1ng \u0111\u01B0\u01A1ng).\n </p>\n }\n </div>\n </div>\n }\n</div>\n", styles: ["@charset \"UTF-8\";.mh-toolbar{margin-bottom:12px;padding:14px 16px;display:flex;flex-direction:column;gap:12px}.mh-toolbar.card{border-radius:0;border:1px solid color-mix(in srgb,var(--border, #e5e7eb) 88%,var(--primary, #5b6cff));background:var(--surface, #fff)}.mh-toolbar__search-row{display:flex;flex-direction:column;gap:6px}.mh-toolbar__search-label{display:flex;align-items:center;gap:8px;font-size:11px;font-weight:800;letter-spacing:.04em;text-transform:uppercase;color:var(--muted, #64748b);margin:0}.mh-toolbar__search-label i{font-size:12px;color:var(--primary, #5b6cff)}.mh-toolbar__search{display:flex;align-items:stretch;gap:8px}.mh-toolbar__search-input{flex:1;min-width:0;padding:10px 12px;border-radius:0;border:1px solid var(--border, #e5e7eb);font-size:15px;font-family:inherit;background:var(--surface, #fff)}.mh-toolbar__search-input:focus{outline:none;border-color:var(--primary, #5b6cff)}.mh-toolbar__search-clear{flex-shrink:0;align-self:center;padding:8px 12px!important}.mh-toolbar__pills{display:flex;gap:6px;overflow-x:auto;padding-bottom:2px;-webkit-overflow-scrolling:touch;scrollbar-width:none}.mh-toolbar__pills::-webkit-scrollbar{display:none}.mh-toolbar__meta{display:flex;align-items:center;justify-content:space-between;gap:10px;padding-top:2px;border-top:1px solid color-mix(in srgb,var(--border, #e5e7eb) 80%,transparent)}.mh-toolbar__count{font-size:12px;font-weight:700;color:var(--muted, #64748b);font-variant-numeric:tabular-nums}.mh-toolbar__sort{display:inline-flex;align-items:center;gap:6px;padding:4px 8px;border:none;background:transparent;font-family:inherit;font-size:12px;font-weight:700;color:var(--primary, #5b6cff);cursor:pointer}.mh-toolbar__sort:hover{text-decoration:underline}.mh-kind-pill{display:inline-flex;align-items:center;gap:5px;flex-shrink:0;padding:6px 11px;border-radius:999px;border:1px solid var(--border, #e5e7eb);background:var(--surface, #fff);font-size:12px;font-weight:600;font-family:inherit;color:var(--text);cursor:pointer;transition:border-color .15s,background .15s}.mh-kind-pill i{font-size:12px;color:var(--chip-color, var(--muted))}.mh-kind-pill__count{font-size:10px;font-weight:800;opacity:.75;font-variant-numeric:tabular-nums}.mh-kind-pill.active{border-color:var(--chip-color, var(--primary, #5b6cff));background:color-mix(in srgb,var(--chip-color, var(--primary, #5b6cff)) 12%,white);font-weight:700}.mh-kind-pill--more{color:var(--muted, #64748b)}.mh-kind-pill--more.active{color:var(--primary, #5b6cff)}.mh-kind-pill:hover:not(.active){border-color:#cbd5e1}.mh-filter{position:relative;z-index:4;margin-bottom:16px}.mh-filter-panel{position:absolute;left:0;right:0;top:0;z-index:5;padding:14px 14px 12px;border-radius:0;border:1px solid color-mix(in srgb,var(--border, #e5e7eb) 80%,var(--primary, #5b6cff));background:var(--surface, #fff);box-shadow:0 20px 48px -24px #0f172a47}.mh-filter-panel__headrow{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:10px}.mh-filter-panel__title{font-size:12px;font-weight:800;letter-spacing:.04em;text-transform:uppercase;color:var(--muted, #64748b);margin:0;flex:1;min-width:0}.mh-filter-panel__reset{flex-shrink:0;white-space:nowrap;cursor:pointer;font-weight:700}.mh-filter-panel__reset i{margin-right:4px}.mh-filter-panel__field{margin-bottom:10px}.mh-filter-panel__label{display:block;font-size:11px;font-weight:700;color:var(--muted, #64748b);margin-bottom:6px}.mh-filter-panel__search{width:100%;padding:9px 12px;border-radius:0;border:1px solid var(--border, #e5e7eb);font-size:14px;font-family:inherit;background:var(--surface, #fff)}.mh-filter-panel__seg{display:flex;flex-wrap:wrap;gap:6px}.mh-filter-panel__seg button{flex:1;min-width:0;padding:8px 10px;border-radius:0;border:1px solid var(--border, #e5e7eb);background:var(--surface-2, #f8fafc);font-size:12px;font-weight:700;font-family:inherit;color:var(--muted, #64748b);cursor:pointer;transition:background .15s,border-color .15s,color .15s}.mh-filter-panel__seg button.active{border-color:var(--primary, #5b6cff);background:color-mix(in srgb,var(--primary, #5b6cff) 14%,white);color:var(--text)}.mh-filter-panel__seg button:hover:not(.active){border-color:#cbd5e1}.mh-filter-panel__kinds{margin-top:4px;max-height:min(52vh,280px);overflow-y:auto;display:flex;flex-direction:column;gap:6px;padding-right:2px}.mh-filter-panel__empty{margin:0;padding:8px 4px 4px;font-size:12px;line-height:1.4;color:var(--muted, #64748b)}.kind-chip{display:inline-flex;align-items:center;gap:6px;padding:8px 12px;border-radius:999px;border:1px solid var(--border, #e5e7eb);background:var(--surface, #fff);font-size:12px;font-weight:600;color:var(--text);cursor:pointer;transition:border-color .15s,background .15s,color .15s;font-family:inherit}.kind-chip i{font-size:13px;color:var(--chip-color, var(--muted))}.kind-chip__count{margin-left:auto;font-size:11px;font-weight:700;opacity:.75;font-variant-numeric:tabular-nums}.kind-chip.active{border-color:var(--chip-color, #5b6cff);background:color-mix(in srgb,var(--chip-color, #5b6cff) 12%,white);color:var(--text)}.kind-chip:hover:not(.active){border-color:#cbd5e1}.kind-chip.mh-filter-kind{width:100%;justify-content:flex-start;border-radius:0}.kind-chip.mh-filter-kind .mh-filter-kind__name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.btn.ghost{border:1px solid var(--border);background:transparent}.btn.ghost.sm{padding:6px 12px;font-size:12px;border-radius:0}\n"] }]
|
|
11447
11622
|
}], propDecorators: { loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], myEntriesCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "myEntriesCount", required: false }] }], visibleCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "visibleCount", required: false }] }], textSearchQuery: [{ type: i0.Input, args: [{ isSignal: true, alias: "textSearchQuery", required: false }] }], filterPanelOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterPanelOpen", required: false }] }], filterPanelKindSearch: [{ type: i0.Input, args: [{ isSignal: true, alias: "filterPanelKindSearch", required: false }] }], timelineSort: [{ type: i0.Input, args: [{ isSignal: true, alias: "timelineSort", required: false }] }], kindListSort: [{ type: i0.Input, args: [{ isSignal: true, alias: "kindListSort", required: false }] }], kindFilterKinds: [{ type: i0.Input, args: [{ isSignal: true, alias: "kindFilterKinds", required: false }] }], kindFilterSummary: [{ type: i0.Input, args: [{ isSignal: true, alias: "kindFilterSummary", required: false }] }], kindsInFilterPanel: [{ type: i0.Input, args: [{ isSignal: true, alias: "kindsInFilterPanel", required: false }] }], kindCounts: [{ type: i0.Input, args: [{ isSignal: true, alias: "kindCounts", required: false }] }], kindFilterKindSet: [{ type: i0.Input, args: [{ isSignal: true, alias: "kindFilterKindSet", required: false }] }], quickKinds: [{ type: i0.Input, args: [{ isSignal: true, alias: "quickKinds", required: false }] }], textSearchQueryChange: [{ type: i0.Output, args: ["textSearchQueryChange"] }], filterPanelOpenChange: [{ type: i0.Output, args: ["filterPanelOpenChange"] }], toggleFilterPanel: [{ type: i0.Output, args: ["toggleFilterPanel"] }], resetFilterPanel: [{ type: i0.Output, args: ["resetFilterPanel"] }], clearTextSearch: [{ type: i0.Output, args: ["clearTextSearch"] }], clearKindFilters: [{ type: i0.Output, args: ["clearKindFilters"] }], toggleKindFilter: [{ type: i0.Output, args: ["toggleKindFilter"] }], timelineSortChange: [{ type: i0.Output, args: ["timelineSortChange"] }], kindListSortChange: [{ type: i0.Output, args: ["kindListSortChange"] }], filterPanelKindSearchChange: [{ type: i0.Output, args: ["filterPanelKindSearchChange"] }] } });
|
|
11448
11623
|
|
|
11624
|
+
class SafeRichHtmlPipe {
|
|
11625
|
+
constructor() {
|
|
11626
|
+
this.sanitizer = inject(DomSanitizer);
|
|
11627
|
+
}
|
|
11628
|
+
transform(value) {
|
|
11629
|
+
return this.sanitizer.bypassSecurityTrustHtml(sanitizeRichHtml(value));
|
|
11630
|
+
}
|
|
11631
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SafeRichHtmlPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
|
|
11632
|
+
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "21.2.17", ngImport: i0, type: SafeRichHtmlPipe, isStandalone: true, name: "safeRichHtml" }); }
|
|
11633
|
+
}
|
|
11634
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SafeRichHtmlPipe, decorators: [{
|
|
11635
|
+
type: Pipe,
|
|
11636
|
+
args: [{
|
|
11637
|
+
name: 'safeRichHtml',
|
|
11638
|
+
standalone: true,
|
|
11639
|
+
pure: true,
|
|
11640
|
+
}]
|
|
11641
|
+
}] });
|
|
11642
|
+
|
|
11449
11643
|
class MedicalHistoryTimelineComponent {
|
|
11450
11644
|
constructor() {
|
|
11451
11645
|
this.timelineGroups = input([], ...(ngDevMode ? [{ debugName: "timelineGroups" }] : /* istanbul ignore next */ []));
|
|
@@ -11481,11 +11675,11 @@ class MedicalHistoryTimelineComponent {
|
|
|
11481
11675
|
img.src = this.placeholderSvg;
|
|
11482
11676
|
}
|
|
11483
11677
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: MedicalHistoryTimelineComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
11484
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: MedicalHistoryTimelineComponent, isStandalone: true, selector: "app-medical-history-timeline", inputs: { timelineGroups: { classPropertyName: "timelineGroups", publicName: "timelineGroups", isSignal: true, isRequired: false, transformFunction: null }, resolveAttachment: { classPropertyName: "resolveAttachment", publicName: "resolveAttachment", isSignal: true, isRequired: false, transformFunction: null }, explorerEnabled: { classPropertyName: "explorerEnabled", publicName: "explorerEnabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { editEntry: "editEntry", deleteEntry: "deleteEntry", viewEntry: "viewEntry", openPreview: "openPreview", openInDocuments: "openInDocuments" }, ngImport: i0, template: "<div class=\"mh-timeline\">\n @for (g of timelineGroups(); track g) {\n <section class=\"mh-month\">\n <header class=\"mh-month__header\">\n <span class=\"mh-month__dot\" aria-hidden=\"true\"></span>\n <h3 class=\"mh-month__title\">{{ g.monthLabel }}</h3>\n <span class=\"mh-month__line\" aria-hidden=\"true\"></span>\n <span class=\"mh-month__count\">{{ g.entries.length }}</span>\n </header>\n <ul class=\"mh-month__entries\">\n @for (e of g.entries; track e) {\n <li\n class=\"mh-card\"\n role=\"button\"\n (click)=\"onViewEntry(e)\"\n tabindex=\"0\"\n (keydown.enter)=\"onViewEntry(e)\"\n >\n <div class=\"mh-card__body\">\n <app-date-chip class=\"mh-card__date\" [date]=\"e.date\" />\n
|
|
11678
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: MedicalHistoryTimelineComponent, isStandalone: true, selector: "app-medical-history-timeline", inputs: { timelineGroups: { classPropertyName: "timelineGroups", publicName: "timelineGroups", isSignal: true, isRequired: false, transformFunction: null }, resolveAttachment: { classPropertyName: "resolveAttachment", publicName: "resolveAttachment", isSignal: true, isRequired: false, transformFunction: null }, explorerEnabled: { classPropertyName: "explorerEnabled", publicName: "explorerEnabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { editEntry: "editEntry", deleteEntry: "deleteEntry", viewEntry: "viewEntry", openPreview: "openPreview", openInDocuments: "openInDocuments" }, ngImport: i0, template: "<div class=\"mh-timeline\">\n @for (g of timelineGroups(); track g) {\n <section class=\"mh-month\">\n <header class=\"mh-month__header\">\n <span class=\"mh-month__dot\" aria-hidden=\"true\"></span>\n <h3 class=\"mh-month__title\">{{ g.monthLabel }}</h3>\n <span class=\"mh-month__line\" aria-hidden=\"true\"></span>\n <span class=\"mh-month__count\">{{ g.entries.length }}</span>\n </header>\n <ul class=\"mh-month__entries\">\n @for (e of g.entries; track e) {\n <li\n class=\"mh-card\"\n role=\"button\"\n (click)=\"onViewEntry(e)\"\n tabindex=\"0\"\n (keydown.enter)=\"onViewEntry(e)\"\n >\n <div class=\"mh-card__body\">\n <div class=\"mh-card__row1\">\n <app-date-chip class=\"mh-card__date\" [date]=\"e.date\" />\n <div class=\"mh-card__main\">\n <div class=\"mh-card__top\">\n <span class=\"mh-kind-badge\" [style.--k]=\"metaFor(e.kind).color\">\n <i class=\"fa-solid\" [ngClass]=\"metaFor(e.kind).icon\" aria-hidden=\"true\"></i>\n {{ metaFor(e.kind).shortLabel }}\n </span>\n </div>\n <h4 class=\"mh-card__title\">{{ e.title }}</h4>\n @if (e.place) {\n <p class=\"mh-card__place\">\n <i class=\"pi pi-map-marker\" aria-hidden=\"true\"></i>{{ e.place }}\n </p>\n }\n </div>\n @if (explorerEnabled()) {\n @if (attachmentFor(e); as att) {\n <div class=\"mh-card__thumb\" (click)=\"$event.stopPropagation()\">\n @if (att.content || att.previewUrl || att.driveFileId) {\n <button\n type=\"button\"\n class=\"mh-card__thumb-btn\"\n (click)=\"onOpenPreview(e)\"\n aria-label=\"Xem \u1EA3nh \u0111\u00EDnh k\u00E8m\"\n >\n @if (att.isLoading) {\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i>\n } @else {\n <img\n class=\"mh-card__thumb-img\"\n [src]=\"att.previewUrl || att.content || placeholderSvg\"\n alt=\"\"\n (error)=\"onImageError($event, att)\"\n />\n }\n </button>\n }\n </div>\n }\n }\n </div>\n\n @if (e.detail) {\n <div class=\"mh-card__detail\" [innerHTML]=\"e.detail | safeRichHtml\"></div>\n }\n\n <div class=\"mh-card__actions\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"btn ghost sm danger\"\n (click)=\"$event.stopPropagation(); deleteEntry.emit(e)\"\n [disabled]=\"!e.rowIndex\"\n >\n <i class=\"pi pi-trash\"></i> Xo\u00E1\n </button>\n <button\n type=\"button\"\n class=\"btn ghost sm\"\n (click)=\"$event.stopPropagation(); editEntry.emit(e)\"\n [disabled]=\"!e.rowIndex\"\n >\n <i class=\"pi pi-pencil\"></i> S\u1EEDa\n </button>\n </div>\n </div>\n <span\n class=\"mh-card__accent\"\n [style.--k]=\"metaFor(e.kind).color\"\n aria-hidden=\"true\"\n ></span>\n </li>\n }\n </ul>\n </section>\n }\n</div>\n", styles: [".mh-timeline{display:flex;flex-direction:column;gap:24px;margin-top:4px;padding-bottom:8px}.mh-month__header{display:flex;align-items:center;gap:10px;margin-bottom:10px;padding:0 2px}.mh-month__dot{width:8px;height:8px;flex-shrink:0;border-radius:50%;background:var(--primary, #5b6cff)}.mh-month__title{margin:0;font-size:14px;font-weight:800;letter-spacing:-.02em;color:var(--text);white-space:nowrap}.mh-month__line{flex:1;height:1px;background:color-mix(in srgb,var(--border, #e5e7eb) 85%,transparent)}.mh-month__count{flex-shrink:0;font-size:12px;font-weight:700;font-variant-numeric:tabular-nums;color:var(--muted, #64748b)}.mh-month__entries{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:10px}.mh-card{position:relative;display:block;width:100%;box-sizing:border-box;border-radius:0;overflow:hidden;border:1px solid color-mix(in srgb,var(--border, #e5e7eb) 88%,var(--primary, #5b6cff));background:var(--surface, #fff);cursor:pointer;transition:border-color .2s ease}.mh-card:hover{border-color:color-mix(in srgb,var(--primary, #5b6cff) 35%,var(--border, #e5e7eb))}.mh-card:focus-visible{outline:2px solid color-mix(in srgb,var(--primary, #5b6cff) 55%,white);outline-offset:2px}.mh-card__accent{position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--k, #64748b)}.mh-card__body{display:flex;flex-direction:column;gap:10px;padding:12px 12px 10px 14px;min-width:0}.mh-card__row1{display:flex;align-items:flex-start;gap:10px;min-width:0}.mh-card__date{flex-shrink:0}.mh-card__main{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}.mh-card__top{display:flex;align-items:center}.mh-kind-badge{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:999px;font-size:11px;font-weight:800;letter-spacing:.02em;background:color-mix(in srgb,var(--k, #64748b) 14%,white);color:var(--text);white-space:nowrap}.mh-kind-badge i{font-size:12px;color:var(--k, #64748b)}.mh-card__title{margin:0;font-size:15px;font-weight:800;line-height:1.3;letter-spacing:-.02em;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.mh-card__place{margin:0;font-size:12px;color:var(--muted);display:flex;align-items:flex-start;gap:6px;line-height:1.4}.mh-card__place i{margin-top:2px;opacity:.85;flex-shrink:0}.mh-card__detail{margin:0;font-size:13px;line-height:1.45;color:var(--text);word-break:break-word;overflow:hidden;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical}.mh-card__detail ::ng-deep p{margin:0;display:inline}.mh-card__detail ::ng-deep p:after{content:\" \"}.mh-card__detail ::ng-deep ul,.mh-card__detail ::ng-deep ol{margin:0;padding-left:1.1em}.mh-card__detail ::ng-deep b,.mh-card__detail ::ng-deep strong{font-weight:700}.mh-card__thumb{flex-shrink:0;width:56px;align-self:flex-start}.mh-card__thumb-btn,.mh-card__thumb-lock{display:flex;align-items:center;justify-content:center;width:56px;height:56px;padding:0;border:1px solid var(--border, #e5e7eb);background:var(--surface-2, #f1f5f9);cursor:pointer;overflow:hidden}.mh-card__thumb-btn{font:inherit;color:var(--muted)}.mh-card__thumb-btn:focus-visible{outline:2px solid var(--primary, #5b6cff);outline-offset:2px}.mh-card__thumb-lock{font-size:14px;color:var(--muted-2, #94a3b8)}.mh-card__thumb-img{display:block;width:100%;height:100%;object-fit:cover}.mh-card__actions{display:flex;align-items:center;justify-content:space-between;gap:10px;padding-top:8px;border-top:1px solid color-mix(in srgb,var(--border, #e5e7eb) 70%,transparent)}.btn.ghost{border:1px solid var(--border);background:transparent}.btn.ghost.sm{padding:5px 10px;font-size:11px;border-radius:0}.btn.ghost.danger{color:#b91c1c;border-color:#fecaca}\n"], dependencies: [{ kind: "component", type: DateChipComponent, selector: "app-date-chip", inputs: ["date", "size", "interactive"] }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "pipe", type: SafeRichHtmlPipe, name: "safeRichHtml" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
11485
11679
|
}
|
|
11486
11680
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: MedicalHistoryTimelineComponent, decorators: [{
|
|
11487
11681
|
type: Component,
|
|
11488
|
-
args: [{ selector: 'app-medical-history-timeline', standalone: true, imports: [DateChipComponent, NgClass], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"mh-timeline\">\n @for (g of timelineGroups(); track g) {\n <section class=\"mh-month\">\n <header class=\"mh-month__header\">\n <span class=\"mh-month__dot\" aria-hidden=\"true\"></span>\n <h3 class=\"mh-month__title\">{{ g.monthLabel }}</h3>\n <span class=\"mh-month__line\" aria-hidden=\"true\"></span>\n <span class=\"mh-month__count\">{{ g.entries.length }}</span>\n </header>\n <ul class=\"mh-month__entries\">\n @for (e of g.entries; track e) {\n <li\n class=\"mh-card\"\n role=\"button\"\n (click)=\"onViewEntry(e)\"\n tabindex=\"0\"\n (keydown.enter)=\"onViewEntry(e)\"\n >\n <div class=\"mh-card__body\">\n <app-date-chip class=\"mh-card__date\" [date]=\"e.date\" />\n
|
|
11682
|
+
args: [{ selector: 'app-medical-history-timeline', standalone: true, imports: [DateChipComponent, NgClass, SafeRichHtmlPipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"mh-timeline\">\n @for (g of timelineGroups(); track g) {\n <section class=\"mh-month\">\n <header class=\"mh-month__header\">\n <span class=\"mh-month__dot\" aria-hidden=\"true\"></span>\n <h3 class=\"mh-month__title\">{{ g.monthLabel }}</h3>\n <span class=\"mh-month__line\" aria-hidden=\"true\"></span>\n <span class=\"mh-month__count\">{{ g.entries.length }}</span>\n </header>\n <ul class=\"mh-month__entries\">\n @for (e of g.entries; track e) {\n <li\n class=\"mh-card\"\n role=\"button\"\n (click)=\"onViewEntry(e)\"\n tabindex=\"0\"\n (keydown.enter)=\"onViewEntry(e)\"\n >\n <div class=\"mh-card__body\">\n <div class=\"mh-card__row1\">\n <app-date-chip class=\"mh-card__date\" [date]=\"e.date\" />\n <div class=\"mh-card__main\">\n <div class=\"mh-card__top\">\n <span class=\"mh-kind-badge\" [style.--k]=\"metaFor(e.kind).color\">\n <i class=\"fa-solid\" [ngClass]=\"metaFor(e.kind).icon\" aria-hidden=\"true\"></i>\n {{ metaFor(e.kind).shortLabel }}\n </span>\n </div>\n <h4 class=\"mh-card__title\">{{ e.title }}</h4>\n @if (e.place) {\n <p class=\"mh-card__place\">\n <i class=\"pi pi-map-marker\" aria-hidden=\"true\"></i>{{ e.place }}\n </p>\n }\n </div>\n @if (explorerEnabled()) {\n @if (attachmentFor(e); as att) {\n <div class=\"mh-card__thumb\" (click)=\"$event.stopPropagation()\">\n @if (att.content || att.previewUrl || att.driveFileId) {\n <button\n type=\"button\"\n class=\"mh-card__thumb-btn\"\n (click)=\"onOpenPreview(e)\"\n aria-label=\"Xem \u1EA3nh \u0111\u00EDnh k\u00E8m\"\n >\n @if (att.isLoading) {\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i>\n } @else {\n <img\n class=\"mh-card__thumb-img\"\n [src]=\"att.previewUrl || att.content || placeholderSvg\"\n alt=\"\"\n (error)=\"onImageError($event, att)\"\n />\n }\n </button>\n }\n </div>\n }\n }\n </div>\n\n @if (e.detail) {\n <div class=\"mh-card__detail\" [innerHTML]=\"e.detail | safeRichHtml\"></div>\n }\n\n <div class=\"mh-card__actions\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"btn ghost sm danger\"\n (click)=\"$event.stopPropagation(); deleteEntry.emit(e)\"\n [disabled]=\"!e.rowIndex\"\n >\n <i class=\"pi pi-trash\"></i> Xo\u00E1\n </button>\n <button\n type=\"button\"\n class=\"btn ghost sm\"\n (click)=\"$event.stopPropagation(); editEntry.emit(e)\"\n [disabled]=\"!e.rowIndex\"\n >\n <i class=\"pi pi-pencil\"></i> S\u1EEDa\n </button>\n </div>\n </div>\n <span\n class=\"mh-card__accent\"\n [style.--k]=\"metaFor(e.kind).color\"\n aria-hidden=\"true\"\n ></span>\n </li>\n }\n </ul>\n </section>\n }\n</div>\n", styles: [".mh-timeline{display:flex;flex-direction:column;gap:24px;margin-top:4px;padding-bottom:8px}.mh-month__header{display:flex;align-items:center;gap:10px;margin-bottom:10px;padding:0 2px}.mh-month__dot{width:8px;height:8px;flex-shrink:0;border-radius:50%;background:var(--primary, #5b6cff)}.mh-month__title{margin:0;font-size:14px;font-weight:800;letter-spacing:-.02em;color:var(--text);white-space:nowrap}.mh-month__line{flex:1;height:1px;background:color-mix(in srgb,var(--border, #e5e7eb) 85%,transparent)}.mh-month__count{flex-shrink:0;font-size:12px;font-weight:700;font-variant-numeric:tabular-nums;color:var(--muted, #64748b)}.mh-month__entries{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:10px}.mh-card{position:relative;display:block;width:100%;box-sizing:border-box;border-radius:0;overflow:hidden;border:1px solid color-mix(in srgb,var(--border, #e5e7eb) 88%,var(--primary, #5b6cff));background:var(--surface, #fff);cursor:pointer;transition:border-color .2s ease}.mh-card:hover{border-color:color-mix(in srgb,var(--primary, #5b6cff) 35%,var(--border, #e5e7eb))}.mh-card:focus-visible{outline:2px solid color-mix(in srgb,var(--primary, #5b6cff) 55%,white);outline-offset:2px}.mh-card__accent{position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--k, #64748b)}.mh-card__body{display:flex;flex-direction:column;gap:10px;padding:12px 12px 10px 14px;min-width:0}.mh-card__row1{display:flex;align-items:flex-start;gap:10px;min-width:0}.mh-card__date{flex-shrink:0}.mh-card__main{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}.mh-card__top{display:flex;align-items:center}.mh-kind-badge{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:999px;font-size:11px;font-weight:800;letter-spacing:.02em;background:color-mix(in srgb,var(--k, #64748b) 14%,white);color:var(--text);white-space:nowrap}.mh-kind-badge i{font-size:12px;color:var(--k, #64748b)}.mh-card__title{margin:0;font-size:15px;font-weight:800;line-height:1.3;letter-spacing:-.02em;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}.mh-card__place{margin:0;font-size:12px;color:var(--muted);display:flex;align-items:flex-start;gap:6px;line-height:1.4}.mh-card__place i{margin-top:2px;opacity:.85;flex-shrink:0}.mh-card__detail{margin:0;font-size:13px;line-height:1.45;color:var(--text);word-break:break-word;overflow:hidden;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical}.mh-card__detail ::ng-deep p{margin:0;display:inline}.mh-card__detail ::ng-deep p:after{content:\" \"}.mh-card__detail ::ng-deep ul,.mh-card__detail ::ng-deep ol{margin:0;padding-left:1.1em}.mh-card__detail ::ng-deep b,.mh-card__detail ::ng-deep strong{font-weight:700}.mh-card__thumb{flex-shrink:0;width:56px;align-self:flex-start}.mh-card__thumb-btn,.mh-card__thumb-lock{display:flex;align-items:center;justify-content:center;width:56px;height:56px;padding:0;border:1px solid var(--border, #e5e7eb);background:var(--surface-2, #f1f5f9);cursor:pointer;overflow:hidden}.mh-card__thumb-btn{font:inherit;color:var(--muted)}.mh-card__thumb-btn:focus-visible{outline:2px solid var(--primary, #5b6cff);outline-offset:2px}.mh-card__thumb-lock{font-size:14px;color:var(--muted-2, #94a3b8)}.mh-card__thumb-img{display:block;width:100%;height:100%;object-fit:cover}.mh-card__actions{display:flex;align-items:center;justify-content:space-between;gap:10px;padding-top:8px;border-top:1px solid color-mix(in srgb,var(--border, #e5e7eb) 70%,transparent)}.btn.ghost{border:1px solid var(--border);background:transparent}.btn.ghost.sm{padding:5px 10px;font-size:11px;border-radius:0}.btn.ghost.danger{color:#b91c1c;border-color:#fecaca}\n"] }]
|
|
11489
11683
|
}], propDecorators: { timelineGroups: [{ type: i0.Input, args: [{ isSignal: true, alias: "timelineGroups", required: false }] }], resolveAttachment: [{ type: i0.Input, args: [{ isSignal: true, alias: "resolveAttachment", required: false }] }], explorerEnabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "explorerEnabled", required: false }] }], editEntry: [{ type: i0.Output, args: ["editEntry"] }], deleteEntry: [{ type: i0.Output, args: ["deleteEntry"] }], viewEntry: [{ type: i0.Output, args: ["viewEntry"] }], openPreview: [{ type: i0.Output, args: ["openPreview"] }], openInDocuments: [{ type: i0.Output, args: ["openInDocuments"] }] } });
|
|
11490
11684
|
|
|
11491
11685
|
class MedicalHistoryPreviewComponent {
|
|
@@ -11746,11 +11940,11 @@ class MedicalHistoryDetailDialogComponent {
|
|
|
11746
11940
|
this.openPreview.emit(this.entry());
|
|
11747
11941
|
}
|
|
11748
11942
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: MedicalHistoryDetailDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
11749
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: MedicalHistoryDetailDialogComponent, isStandalone: true, selector: "app-medical-history-detail-dialog", inputs: { entry: { classPropertyName: "entry", publicName: "entry", isSignal: true, isRequired: true, transformFunction: null }, attachment: { classPropertyName: "attachment", publicName: "attachment", isSignal: true, isRequired: false, transformFunction: null }, explorerEnabled: { classPropertyName: "explorerEnabled", publicName: "explorerEnabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { close: "close", edit: "edit", delete: "delete", openPreview: "openPreview" }, ngImport: i0, template: "<div class=\"dialog-overlay\" (click)=\"onClose()\">\n <div\n class=\"dialog mh-detail-dialog\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"'Chi ti\u1EBFt: ' + entry().title\"\n (click)=\"$event.stopPropagation()\"\n >\n <div class=\"dialog-head mh-detail-dialog__head\">\n <app-date-chip class=\"mh-detail-dialog__date-chip\" [date]=\"entry().date\" size=\"md\" />\n <div class=\"mh-detail-dialog__title-wrap\">\n <span class=\"mh-kind-badge\" [style.--k]=\"metaFor(entry().kind).color\">\n <i class=\"fa-solid\" [ngClass]=\"metaFor(entry().kind).icon\" aria-hidden=\"true\"></i>\n {{ metaFor(entry().kind).label }}\n </span>\n <h2 class=\"mh-detail-dialog__title\">{{ entry().title }}</h2>\n </div>\n <button type=\"button\" class=\"dialog-close\" (click)=\"onClose()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n\n <div class=\"mh-detail-dialog__body\">\n @if (entry().detail) {\n <section class=\"mh-detail-dialog__section\">\n <h3 class=\"mh-detail-dialog__label\">Chi ti\u1EBFt</h3>\n <
|
|
11943
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: MedicalHistoryDetailDialogComponent, isStandalone: true, selector: "app-medical-history-detail-dialog", inputs: { entry: { classPropertyName: "entry", publicName: "entry", isSignal: true, isRequired: true, transformFunction: null }, attachment: { classPropertyName: "attachment", publicName: "attachment", isSignal: true, isRequired: false, transformFunction: null }, explorerEnabled: { classPropertyName: "explorerEnabled", publicName: "explorerEnabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { close: "close", edit: "edit", delete: "delete", openPreview: "openPreview" }, ngImport: i0, template: "<div class=\"dialog-overlay\" (click)=\"onClose()\">\n <div\n class=\"dialog mh-detail-dialog\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"'Chi ti\u1EBFt: ' + entry().title\"\n (click)=\"$event.stopPropagation()\"\n >\n <div class=\"dialog-head mh-detail-dialog__head\">\n <app-date-chip class=\"mh-detail-dialog__date-chip\" [date]=\"entry().date\" size=\"md\" />\n <div class=\"mh-detail-dialog__title-wrap\">\n <span class=\"mh-kind-badge\" [style.--k]=\"metaFor(entry().kind).color\">\n <i class=\"fa-solid\" [ngClass]=\"metaFor(entry().kind).icon\" aria-hidden=\"true\"></i>\n {{ metaFor(entry().kind).label }}\n </span>\n <h2 class=\"mh-detail-dialog__title\">{{ entry().title }}</h2>\n </div>\n <button type=\"button\" class=\"dialog-close\" (click)=\"onClose()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n\n <div class=\"mh-detail-dialog__body\">\n @if (entry().detail) {\n <section class=\"mh-detail-dialog__section\">\n <h3 class=\"mh-detail-dialog__label\">Chi ti\u1EBFt</h3>\n <div class=\"mh-detail-dialog__text\" [innerHTML]=\"entry().detail | safeRichHtml\"></div>\n </section>\n }\n\n @if (entry().place) {\n <section class=\"mh-detail-dialog__section\">\n <h3 class=\"mh-detail-dialog__label\">N\u01A1i kh\u00E1m</h3>\n <p class=\"mh-detail-dialog__text mh-detail-dialog__place\">\n <i class=\"pi pi-map-marker\" aria-hidden=\"true\"></i>\n {{ entry().place }}\n </p>\n </section>\n }\n\n @if (attachment(); as att) {\n @if (explorerEnabled()) {\n <section class=\"mh-detail-dialog__section\">\n <h3 class=\"mh-detail-dialog__label\">\u1EA2nh \u0111\u00EDnh k\u00E8m</h3>\n <button type=\"button\" class=\"mh-detail-dialog__thumb\" (click)=\"onOpenPreview()\">\n @if (att.isLoading) {\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i>\n } @else if (att.previewUrl || att.content) {\n <img [src]=\"att.previewUrl || att.content\" alt=\"\u1EA2nh \u0111\u00EDnh k\u00E8m\" />\n } @else {\n <i class=\"pi pi-image\" aria-hidden=\"true\"></i>\n <span>Xem \u1EA3nh</span>\n }\n </button>\n </section>\n }\n }\n </div>\n\n <div class=\"dialog-actions mh-detail-dialog__actions\">\n <div class=\"mh-detail-dialog__actions-start\">\n <button\n type=\"button\"\n class=\"btn ghost danger\"\n (click)=\"onDelete()\"\n [disabled]=\"!entry().rowIndex\"\n >\n <i class=\"pi pi-trash\"></i> Xo\u00E1\n </button>\n <button\n type=\"button\"\n class=\"btn primary\"\n (click)=\"onEdit()\"\n [disabled]=\"!entry().rowIndex\"\n >\n <i class=\"pi pi-pencil\"></i> S\u1EEDa\n </button>\n </div>\n <button type=\"button\" class=\"btn ghost\" (click)=\"onClose()\">\u0110\u00F3ng</button>\n </div>\n </div>\n</div>\n", styles: [":host{--surface: #ffffff;--surface-2: #f8fafc;--border: #e6e8ef;--primary: #5b6cff;--text: #0f172a;--muted: #64748b;--muted-2: #94a3b8;display:block;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;color:var(--text)}.dialog-overlay{position:fixed;inset:0;z-index:250;background:#0f172a73;display:flex;align-items:center;justify-content:center;padding:16px;padding-top:max(16px,env(safe-area-inset-top,0px));padding-bottom:max(16px,env(safe-area-inset-bottom,0px));box-sizing:border-box}@media(max-width:639px){.dialog-overlay{padding-bottom:calc(68px + env(safe-area-inset-bottom,0px));align-items:center}}.dialog{width:100%;background:var(--surface);box-shadow:0 24px 48px -24px #0f172a59}.dialog-head{display:flex;justify-content:space-between;align-items:flex-start;gap:12px}.dialog-close{flex-shrink:0;border:none;background:transparent;padding:8px;cursor:pointer;color:var(--muted);border-radius:10px;font-size:16px;line-height:1}.dialog-close:hover{background:var(--surface-2)}.dialog-actions{display:flex;justify-content:flex-end;align-items:center;gap:10px}.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;font-size:14px;font-weight:700;font-family:inherit;cursor:pointer;-webkit-tap-highlight-color:transparent}.btn:disabled{opacity:.55;cursor:not-allowed}.btn.ghost{border:1px solid var(--border);background:transparent;color:var(--text);padding:10px 16px;border-radius:12px}.btn.ghost.danger{color:#b91c1c;border-color:#fecaca}.btn.primary{background:var(--primary);color:#fff;border:none;padding:10px 16px;border-radius:12px}.mh-detail-dialog{max-width:min(520px,100vw - 24px);max-height:min(100dvh - 32px,640px);overflow:hidden;display:flex;flex-direction:column;padding:0;border-radius:16px}@media(max-width:639px){.mh-detail-dialog{max-height:min(100dvh - 88px,640px)}}.mh-detail-dialog__head{flex-shrink:0;display:flex;align-items:flex-start;gap:12px;padding:16px 16px 12px;margin:0;border-bottom:1px solid var(--border)}.mh-detail-dialog__date-chip{flex-shrink:0}.mh-detail-dialog__title-wrap{min-width:0;display:flex;flex-direction:column;gap:6px}.mh-detail-dialog__title{margin:0;font-size:1.1rem;font-weight:800;line-height:1.3;color:var(--text);word-break:break-word}.mh-detail-dialog__body{flex:1;min-height:0;overflow-y:auto;padding:14px 16px;display:flex;flex-direction:column;gap:14px}.mh-detail-dialog__section{display:flex;flex-direction:column;gap:6px}.mh-detail-dialog__label{margin:0;font-size:11px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;color:var(--muted-2)}.mh-detail-dialog__text{margin:0;font-size:14px;line-height:1.55;color:var(--text);word-break:break-word}.mh-detail-dialog__text ::ng-deep p{margin:0 0 .45em}.mh-detail-dialog__text ::ng-deep p:last-child{margin-bottom:0}.mh-detail-dialog__text ::ng-deep ul,.mh-detail-dialog__text ::ng-deep ol{margin:.35em 0;padding-left:1.35em}.mh-detail-dialog__text ::ng-deep b,.mh-detail-dialog__text ::ng-deep strong{font-weight:700}.mh-detail-dialog__place{display:flex;align-items:flex-start;gap:6px}.mh-detail-dialog__place i{margin-top:2px;color:var(--primary)}.mh-detail-dialog__thumb{width:100%;max-width:220px;aspect-ratio:4/3;border:1px solid var(--border);border-radius:12px;overflow:hidden;background:var(--surface-2);display:inline-flex;align-items:center;justify-content:center;gap:8px;color:var(--muted);cursor:pointer;padding:0}.mh-detail-dialog__thumb img{width:100%;height:100%;object-fit:cover;display:block}.mh-detail-dialog__actions{flex-shrink:0;display:flex;align-items:center;justify-content:space-between;gap:10px;padding:12px 16px 16px;border-top:1px solid var(--border);background:var(--surface)}.mh-detail-dialog__actions-start{display:flex;align-items:center;gap:10px;min-width:0}.mh-kind-badge{display:inline-flex;align-items:center;gap:6px;width:fit-content;padding:4px 10px;border-radius:999px;font-size:11px;font-weight:700;color:var(--k, var(--primary));background:color-mix(in srgb,var(--k, #5b6cff) 12%,white);border:1px solid color-mix(in srgb,var(--k, #5b6cff) 24%,transparent)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: DateChipComponent, selector: "app-date-chip", inputs: ["date", "size", "interactive"] }, { kind: "pipe", type: SafeRichHtmlPipe, name: "safeRichHtml" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
11750
11944
|
}
|
|
11751
11945
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: MedicalHistoryDetailDialogComponent, decorators: [{
|
|
11752
11946
|
type: Component,
|
|
11753
|
-
args: [{ selector: 'app-medical-history-detail-dialog', standalone: true, imports: [CommonModule, DateChipComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"dialog-overlay\" (click)=\"onClose()\">\n <div\n class=\"dialog mh-detail-dialog\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"'Chi ti\u1EBFt: ' + entry().title\"\n (click)=\"$event.stopPropagation()\"\n >\n <div class=\"dialog-head mh-detail-dialog__head\">\n <app-date-chip class=\"mh-detail-dialog__date-chip\" [date]=\"entry().date\" size=\"md\" />\n <div class=\"mh-detail-dialog__title-wrap\">\n <span class=\"mh-kind-badge\" [style.--k]=\"metaFor(entry().kind).color\">\n <i class=\"fa-solid\" [ngClass]=\"metaFor(entry().kind).icon\" aria-hidden=\"true\"></i>\n {{ metaFor(entry().kind).label }}\n </span>\n <h2 class=\"mh-detail-dialog__title\">{{ entry().title }}</h2>\n </div>\n <button type=\"button\" class=\"dialog-close\" (click)=\"onClose()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n\n <div class=\"mh-detail-dialog__body\">\n @if (entry().detail) {\n <section class=\"mh-detail-dialog__section\">\n <h3 class=\"mh-detail-dialog__label\">Chi ti\u1EBFt</h3>\n <
|
|
11947
|
+
args: [{ selector: 'app-medical-history-detail-dialog', standalone: true, imports: [CommonModule, DateChipComponent, SafeRichHtmlPipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"dialog-overlay\" (click)=\"onClose()\">\n <div\n class=\"dialog mh-detail-dialog\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"'Chi ti\u1EBFt: ' + entry().title\"\n (click)=\"$event.stopPropagation()\"\n >\n <div class=\"dialog-head mh-detail-dialog__head\">\n <app-date-chip class=\"mh-detail-dialog__date-chip\" [date]=\"entry().date\" size=\"md\" />\n <div class=\"mh-detail-dialog__title-wrap\">\n <span class=\"mh-kind-badge\" [style.--k]=\"metaFor(entry().kind).color\">\n <i class=\"fa-solid\" [ngClass]=\"metaFor(entry().kind).icon\" aria-hidden=\"true\"></i>\n {{ metaFor(entry().kind).label }}\n </span>\n <h2 class=\"mh-detail-dialog__title\">{{ entry().title }}</h2>\n </div>\n <button type=\"button\" class=\"dialog-close\" (click)=\"onClose()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n\n <div class=\"mh-detail-dialog__body\">\n @if (entry().detail) {\n <section class=\"mh-detail-dialog__section\">\n <h3 class=\"mh-detail-dialog__label\">Chi ti\u1EBFt</h3>\n <div class=\"mh-detail-dialog__text\" [innerHTML]=\"entry().detail | safeRichHtml\"></div>\n </section>\n }\n\n @if (entry().place) {\n <section class=\"mh-detail-dialog__section\">\n <h3 class=\"mh-detail-dialog__label\">N\u01A1i kh\u00E1m</h3>\n <p class=\"mh-detail-dialog__text mh-detail-dialog__place\">\n <i class=\"pi pi-map-marker\" aria-hidden=\"true\"></i>\n {{ entry().place }}\n </p>\n </section>\n }\n\n @if (attachment(); as att) {\n @if (explorerEnabled()) {\n <section class=\"mh-detail-dialog__section\">\n <h3 class=\"mh-detail-dialog__label\">\u1EA2nh \u0111\u00EDnh k\u00E8m</h3>\n <button type=\"button\" class=\"mh-detail-dialog__thumb\" (click)=\"onOpenPreview()\">\n @if (att.isLoading) {\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i>\n } @else if (att.previewUrl || att.content) {\n <img [src]=\"att.previewUrl || att.content\" alt=\"\u1EA2nh \u0111\u00EDnh k\u00E8m\" />\n } @else {\n <i class=\"pi pi-image\" aria-hidden=\"true\"></i>\n <span>Xem \u1EA3nh</span>\n }\n </button>\n </section>\n }\n }\n </div>\n\n <div class=\"dialog-actions mh-detail-dialog__actions\">\n <div class=\"mh-detail-dialog__actions-start\">\n <button\n type=\"button\"\n class=\"btn ghost danger\"\n (click)=\"onDelete()\"\n [disabled]=\"!entry().rowIndex\"\n >\n <i class=\"pi pi-trash\"></i> Xo\u00E1\n </button>\n <button\n type=\"button\"\n class=\"btn primary\"\n (click)=\"onEdit()\"\n [disabled]=\"!entry().rowIndex\"\n >\n <i class=\"pi pi-pencil\"></i> S\u1EEDa\n </button>\n </div>\n <button type=\"button\" class=\"btn ghost\" (click)=\"onClose()\">\u0110\u00F3ng</button>\n </div>\n </div>\n</div>\n", styles: [":host{--surface: #ffffff;--surface-2: #f8fafc;--border: #e6e8ef;--primary: #5b6cff;--text: #0f172a;--muted: #64748b;--muted-2: #94a3b8;display:block;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;color:var(--text)}.dialog-overlay{position:fixed;inset:0;z-index:250;background:#0f172a73;display:flex;align-items:center;justify-content:center;padding:16px;padding-top:max(16px,env(safe-area-inset-top,0px));padding-bottom:max(16px,env(safe-area-inset-bottom,0px));box-sizing:border-box}@media(max-width:639px){.dialog-overlay{padding-bottom:calc(68px + env(safe-area-inset-bottom,0px));align-items:center}}.dialog{width:100%;background:var(--surface);box-shadow:0 24px 48px -24px #0f172a59}.dialog-head{display:flex;justify-content:space-between;align-items:flex-start;gap:12px}.dialog-close{flex-shrink:0;border:none;background:transparent;padding:8px;cursor:pointer;color:var(--muted);border-radius:10px;font-size:16px;line-height:1}.dialog-close:hover{background:var(--surface-2)}.dialog-actions{display:flex;justify-content:flex-end;align-items:center;gap:10px}.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;font-size:14px;font-weight:700;font-family:inherit;cursor:pointer;-webkit-tap-highlight-color:transparent}.btn:disabled{opacity:.55;cursor:not-allowed}.btn.ghost{border:1px solid var(--border);background:transparent;color:var(--text);padding:10px 16px;border-radius:12px}.btn.ghost.danger{color:#b91c1c;border-color:#fecaca}.btn.primary{background:var(--primary);color:#fff;border:none;padding:10px 16px;border-radius:12px}.mh-detail-dialog{max-width:min(520px,100vw - 24px);max-height:min(100dvh - 32px,640px);overflow:hidden;display:flex;flex-direction:column;padding:0;border-radius:16px}@media(max-width:639px){.mh-detail-dialog{max-height:min(100dvh - 88px,640px)}}.mh-detail-dialog__head{flex-shrink:0;display:flex;align-items:flex-start;gap:12px;padding:16px 16px 12px;margin:0;border-bottom:1px solid var(--border)}.mh-detail-dialog__date-chip{flex-shrink:0}.mh-detail-dialog__title-wrap{min-width:0;display:flex;flex-direction:column;gap:6px}.mh-detail-dialog__title{margin:0;font-size:1.1rem;font-weight:800;line-height:1.3;color:var(--text);word-break:break-word}.mh-detail-dialog__body{flex:1;min-height:0;overflow-y:auto;padding:14px 16px;display:flex;flex-direction:column;gap:14px}.mh-detail-dialog__section{display:flex;flex-direction:column;gap:6px}.mh-detail-dialog__label{margin:0;font-size:11px;font-weight:800;letter-spacing:.06em;text-transform:uppercase;color:var(--muted-2)}.mh-detail-dialog__text{margin:0;font-size:14px;line-height:1.55;color:var(--text);word-break:break-word}.mh-detail-dialog__text ::ng-deep p{margin:0 0 .45em}.mh-detail-dialog__text ::ng-deep p:last-child{margin-bottom:0}.mh-detail-dialog__text ::ng-deep ul,.mh-detail-dialog__text ::ng-deep ol{margin:.35em 0;padding-left:1.35em}.mh-detail-dialog__text ::ng-deep b,.mh-detail-dialog__text ::ng-deep strong{font-weight:700}.mh-detail-dialog__place{display:flex;align-items:flex-start;gap:6px}.mh-detail-dialog__place i{margin-top:2px;color:var(--primary)}.mh-detail-dialog__thumb{width:100%;max-width:220px;aspect-ratio:4/3;border:1px solid var(--border);border-radius:12px;overflow:hidden;background:var(--surface-2);display:inline-flex;align-items:center;justify-content:center;gap:8px;color:var(--muted);cursor:pointer;padding:0}.mh-detail-dialog__thumb img{width:100%;height:100%;object-fit:cover;display:block}.mh-detail-dialog__actions{flex-shrink:0;display:flex;align-items:center;justify-content:space-between;gap:10px;padding:12px 16px 16px;border-top:1px solid var(--border);background:var(--surface)}.mh-detail-dialog__actions-start{display:flex;align-items:center;gap:10px;min-width:0}.mh-kind-badge{display:inline-flex;align-items:center;gap:6px;width:fit-content;padding:4px 10px;border-radius:999px;font-size:11px;font-weight:700;color:var(--k, var(--primary));background:color-mix(in srgb,var(--k, #5b6cff) 12%,white);border:1px solid color-mix(in srgb,var(--k, #5b6cff) 24%,transparent)}\n"] }]
|
|
11754
11948
|
}], propDecorators: { entry: [{ type: i0.Input, args: [{ isSignal: true, alias: "entry", required: true }] }], attachment: [{ type: i0.Input, args: [{ isSignal: true, alias: "attachment", required: false }] }], explorerEnabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "explorerEnabled", required: false }] }], close: [{ type: i0.Output, args: ["close"] }], edit: [{ type: i0.Output, args: ["edit"] }], delete: [{ type: i0.Output, args: ["delete"] }], openPreview: [{ type: i0.Output, args: ["openPreview"] }] } });
|
|
11755
11949
|
|
|
11756
11950
|
const EXPLORER_ROOT_ID = 1;
|
|
@@ -12209,7 +12403,7 @@ class MedicalHistoryComponent {
|
|
|
12209
12403
|
entryMatchesFreeText(e, query) {
|
|
12210
12404
|
const m = kindMeta(e.kind);
|
|
12211
12405
|
return (viFoldIncludes(e.title, query) ||
|
|
12212
|
-
viFoldIncludes(e.detail, query) ||
|
|
12406
|
+
viFoldIncludes(stripHtml(e.detail), query) ||
|
|
12213
12407
|
viFoldIncludes(e.place || '', query) ||
|
|
12214
12408
|
viFoldIncludes(m.label, query) ||
|
|
12215
12409
|
viFoldIncludes(m.shortLabel, query) ||
|
|
@@ -21114,66 +21308,13 @@ class DailySummaryComponent {
|
|
|
21114
21308
|
});
|
|
21115
21309
|
}
|
|
21116
21310
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DailySummaryComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
21117
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: DailySummaryComponent, isStandalone: true, selector: "app-daily-summary", inputs: { logs: { classPropertyName: "logs", publicName: "logs", isSignal: true, isRequired: false, transformFunction: null }, birthDate: { classPropertyName: "birthDate", publicName: "birthDate", isSignal: true, isRequired: false, transformFunction: null }, initialDate: { classPropertyName: "initialDate", publicName: "initialDate", isSignal: true, isRequired: false, transformFunction: null }, feedGroupGapMinutes: { classPropertyName: "feedGroupGapMinutes", publicName: "feedGroupGapMinutes", isSignal: true, isRequired: false, transformFunction: null }, gender: { classPropertyName: "gender", publicName: "gender", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@let s = summary();\n\n<div class=\"ds\">\n <!-- \u0110i\u1EC1u h\u01B0\u1EDBng ng\u00E0y -->\n <div class=\"ds-datebar\" [class.ds-datebar--weekend]=\"s.weekend\">\n <button type=\"button\" class=\"ds-datebar__nav\" (click)=\"shiftDay(-1)\" aria-label=\"Ng\u00E0y tr\u01B0\u1EDBc\">\n <i class=\"pi pi-chevron-left\" aria-hidden=\"true\"></i>\n </button>\n\n <label class=\"ds-datebar__center\">\n <span class=\"ds-datebar__label\">\n {{ s.weekday }}, {{ s.displayDate }}\n </span>\n @if (s.weekend) {\n <span class=\"ds-datebar__badge\">Cu\u1ED1i tu\u1EA7n</span>\n }\n <input\n type=\"date\"\n class=\"ds-datebar__input\"\n [value]=\"s.date\"\n [max]=\"maxDate\"\n (change)=\"onDateInput($any($event.target).value)\"\n aria-label=\"Ch\u1ECDn ng\u00E0y\"\n />\n </label>\n\n <button\n type=\"button\"\n class=\"ds-datebar__nav\"\n [disabled]=\"!canGoForward()\"\n (click)=\"shiftDay(1)\"\n aria-label=\"Ng\u00E0y sau\"\n >\n <i class=\"pi pi-chevron-right\" aria-hidden=\"true\"></i>\n </button>\n </div>\n\n @if (canGoForward()) {\n <button type=\"button\" class=\"ds-reset\" (click)=\"goLatest()\">\n <i class=\"pi pi-replay\" aria-hidden=\"true\"></i>\n V\u1EC1 ng\u00E0y m\u1EDBi nh\u1EA5t ({{ maxDateLabel }})\n </button>\n }\n\n <!-- T\u1ED5ng quan -->\n <section class=\"ds-hero\" aria-label=\"T\u1ED5ng quan trong ng\u00E0y\">\n <div class=\"ds-hero__top\">\n <div>\n <div class=\"ds-hero__label\">T\u1ED4NG L\u01AF\u1EE2NG S\u1EEEA TRONG NG\u00C0Y</div>\n <div class=\"ds-hero__total\">{{ s.totalMl }}<span class=\"ds-hero__unit\">ml</span></div>\n @if (s.hasFeeding || s.yesterday.totalMl > 0) {\n <div\n class=\"ds-hero__delta\"\n [class.ds-hero__delta--up]=\"s.yesterday.deltaMl > 0\"\n [class.ds-hero__delta--down]=\"s.yesterday.deltaMl < 0\"\n [class.ds-hero__delta--flat]=\"s.yesterday.deltaMl === 0\"\n >\n @if (s.yesterday.totalMl === 0 && s.hasFeeding) {\n H\u00F4m qua ch\u01B0a ghi nh\u1EADn c\u1EEF\n } @else if (s.yesterday.deltaMl === 0) {\n B\u1EB1ng h\u00F4m qua ({{ s.yesterday.totalMl }}ml)\n } @else {\n <i\n class=\"pi\"\n [class.pi-arrow-up]=\"s.yesterday.deltaMl > 0\"\n [class.pi-arrow-down]=\"s.yesterday.deltaMl < 0\"\n aria-hidden=\"true\"\n ></i>\n {{ s.yesterday.deltaMl > 0 ? '+' : '' }}{{ s.yesterday.deltaMl }}ml\n @if (s.yesterday.deltaPercent !== null) {\n <span class=\"ds-hero__delta-pct\">\n ({{ s.yesterday.deltaPercent > 0 ? '+' : '' }}{{ s.yesterday.deltaPercent }}%)\n </span>\n }\n so v\u1EDBi h\u00F4m qua\n }\n </div>\n }\n </div>\n <div class=\"ds-hero__right\">\n <div class=\"ds-hero__count\">{{ s.feedCount }} c\u1EEF b\u00FA</div>\n <div class=\"ds-hero__avg\">\n @if (s.feedCount > 0) {\n trung b\u00ECnh {{ s.avgMl }}ml m\u1ED7i c\u1EEF\n } @else {\n ch\u01B0a ghi nh\u1EADn c\u1EEF n\u00E0o\n }\n </div>\n </div>\n </div>\n\n <div class=\"ds-hero__stats\">\n <div class=\"ds-hero__stat\">\n <span class=\"ds-hero__stat-label\">C\u1EEF cao nh\u1EA5t</span>\n <span class=\"ds-hero__stat-value ds-hero__stat-value--green\">\n {{ s.hasFeeding ? s.maxMl + 'ml' : '\u2014' }}\n </span>\n </div>\n <div class=\"ds-hero__stat\">\n <span class=\"ds-hero__stat-label\">C\u1EEF th\u1EA5p nh\u1EA5t</span>\n <span class=\"ds-hero__stat-value ds-hero__stat-value--blue\">\n {{ s.hasFeeding ? s.minMl + 'ml' : '\u2014' }}\n </span>\n </div>\n <div class=\"ds-hero__stat\">\n <span class=\"ds-hero__stat-label\">B\u00FA \u0111\u00EAm</span>\n <span\n class=\"ds-hero__stat-value\"\n [class.ds-hero__stat-value--indigo]=\"s.nightCount > 0\"\n [class.ds-hero__stat-value--green]=\"s.nightCount === 0\"\n >\n {{ s.nightCount > 0 ? s.nightCount + ' c\u1EEF' : 'Kh\u00F4ng c\u00F3' }}\n </span>\n </div>\n </div>\n </section>\n\n @if (s.nightCount > 0) {\n <div class=\"ds-night\">\n B\u00E9 c\u00F3 b\u00FA \u0111\u00EAm trong khung 00:00 \u2013 05:00: {{ s.nightCount }} c\u1EEF b\u00FA, t\u1ED5ng {{ s.nightMl }}ml\n </div>\n }\n\n <!-- D\u1EA3i hai tu\u1EA7n -->\n <section class=\"ds-strip\" aria-label=\"D\u1EA3i hai tu\u1EA7n\">\n <div class=\"ds-strip__frame\">\n <button\n type=\"button\"\n class=\"ds-strip__nav\"\n (click)=\"shiftStripWeek(-2)\"\n aria-label=\"Tu\u1EA7n tr\u01B0\u1EDBc\"\n >\n <i class=\"pi pi-chevron-left\" aria-hidden=\"true\"></i>\n </button>\n\n <div class=\"ds-strip__content\">\n <div class=\"ds-strip__chart\" aria-hidden=\"true\">\n @for (cell of s.strip; track cell.date) {\n <div\n class=\"ds-strip__bar-col\"\n [class.ds-strip__bar-col--current]=\"cell.current\"\n [class.ds-strip__bar-col--weekend]=\"cell.weekend\"\n >\n <div class=\"ds-strip__bar-track\">\n <span\n class=\"ds-strip__bar\"\n [class.ds-strip__bar--empty]=\"!cell.hasFeeding\"\n [style.height.%]=\"cell.barPercent\"\n ></span>\n </div>\n @if (cell.hasFeeding) {\n <span class=\"ds-strip__bar-label\">{{ cell.totalMl }}</span>\n }\n </div>\n }\n </div>\n\n <div class=\"ds-strip__row\">\n @for (cell of s.strip; track cell.date) {\n <button\n type=\"button\"\n class=\"ds-strip__cell\"\n [class.ds-strip__cell--current]=\"cell.current\"\n [class.ds-strip__cell--weekend]=\"cell.weekend\"\n [disabled]=\"!isNavigable(cell.date)\"\n (click)=\"openDate(cell.date)\"\n [attr.aria-current]=\"cell.current ? 'date' : null\"\n [attr.aria-label]=\"cell.weekdayShort + ' ng\u00E0y ' + cell.dayNumber\"\n >\n <span class=\"ds-strip__dow\">{{ cell.weekdayShort }}</span>\n <span class=\"ds-strip__day\" [class.ds-strip__day--data]=\"cell.hasFeeding\">\n {{ cell.dayNumber }}\n </span>\n <span\n class=\"ds-strip__dot\"\n [class.ds-strip__dot--activity]=\"cell.hasActivity\"\n [class.ds-strip__dot--event]=\"!cell.hasActivity && cell.hasEvent\"\n ></span>\n </button>\n }\n </div>\n </div>\n\n <button\n type=\"button\"\n class=\"ds-strip__nav\"\n [disabled]=\"!canShiftStripForward()\"\n (click)=\"shiftStripWeek(2)\"\n aria-label=\"Tu\u1EA7n sau\"\n >\n <i class=\"pi pi-chevron-right\" aria-hidden=\"true\"></i>\n </button>\n </div>\n\n <div class=\"ds-strip__legend\">\n <span><i class=\"ds-strip__dot ds-strip__dot--activity\"></i> C\u00F3 ho\u1EA1t \u0111\u1ED9ng</span>\n <span><i class=\"ds-strip__dot ds-strip__dot--event\"></i> C\u00F3 l\u1ECBch h\u1EB9n</span>\n </div>\n </section>\n\n <!-- Th\u1EC3 tr\u1EA1ng & m\u1EE9c \u0111\u1EA1t khuy\u1EBFn ngh\u1ECB -->\n @if (s.body) {\n <section class=\"ds-body-card\" aria-label=\"Th\u1EC3 tr\u1EA1ng v\u00E0 khuy\u1EBFn ngh\u1ECB\">\n <div class=\"ds-body-card__title\">Th\u1EC3 tr\u1EA1ng & khuy\u1EBFn ngh\u1ECB</div>\n\n <div class=\"ds-growth\">\n <article\n class=\"ds-growth__card\"\n [attr.data-status]=\"s.body.weightWho?.status ?? 'unknown'\"\n >\n <div class=\"ds-growth__head\">\n <span class=\"ds-growth__label\">\n <i class=\"pi pi-heart-fill\" aria-hidden=\"true\"></i>\n C\u00E2n n\u1EB7ng\n </span>\n @if (s.body.weightWho; as ww) {\n <span class=\"ds-growth__badge\" [attr.data-status]=\"ww.status\">\n {{ ww.badgeLabel }}\n </span>\n }\n </div>\n <div class=\"ds-growth__value\">{{ s.body.weightKg }}<span>kg</span></div>\n\n @if (s.body.weightWho; as ww) {\n <div class=\"ds-growth__compare\">\n <span>Hi\u1EC7n t\u1EA1i {{ ww.actual }}kg</span>\n <span>Trung v\u1ECB WHO {{ ww.median }}kg</span>\n </div>\n <div class=\"ds-growth__scale\" aria-hidden=\"true\">\n <div class=\"ds-growth__scale-labels\">\n <span>Th\u1EA5p</span>\n <span class=\"ds-growth__scale-mid\">Ph\u00F9 h\u1EE3p</span>\n <span>Cao</span>\n </div>\n <div class=\"ds-growth__scale-track\">\n <span class=\"ds-growth__band ds-growth__band--low\"></span>\n <span class=\"ds-growth__band ds-growth__band--mid\"></span>\n <span class=\"ds-growth__band ds-growth__band--high\"></span>\n <span\n class=\"ds-growth__marker\"\n [attr.data-status]=\"ww.status\"\n [style.left.%]=\"whoMarkerPosition(ww.zScore)\"\n ></span>\n </div>\n <div class=\"ds-growth__scale-legend\">\n <span>\u22122 SD</span>\n <span>P{{ ww.percentile }} \u00B7 z {{ formatWhoZ(ww.zScore) }}</span>\n <span>+2 SD</span>\n </div>\n </div>\n <div class=\"ds-growth__delta\" [attr.data-status]=\"ww.status\">\n {{ ww.deltaLabel }}\n </div>\n } @else {\n <div class=\"ds-growth__empty\">\n @if (s.body.ageInDays !== null) {\n Th\u00EAm gi\u1EDBi t\u00EDnh trong profile \u0111\u1EC3 so chu\u1EA9n WHO\n } @else {\n C\u1EA7n ng\u00E0y sinh \u0111\u1EC3 so chu\u1EA9n WHO\n }\n </div>\n }\n </article>\n\n <article\n class=\"ds-growth__card\"\n [attr.data-status]=\"s.body.heightWho?.status ?? 'unknown'\"\n >\n <div class=\"ds-growth__head\">\n <span class=\"ds-growth__label\">\n <i class=\"pi pi-arrows-v\" aria-hidden=\"true\"></i>\n Chi\u1EC1u cao\n </span>\n @if (s.body.heightWho; as hw) {\n <span class=\"ds-growth__badge\" [attr.data-status]=\"hw.status\">\n {{ hw.badgeLabel }}\n </span>\n }\n </div>\n <div class=\"ds-growth__value\">\n @if (s.body.heightCm) {\n {{ s.body.heightCm }}<span>cm</span>\n } @else {\n \u2014<span>cm</span>\n }\n </div>\n\n @if (s.body.heightWho; as hw) {\n <div class=\"ds-growth__compare\">\n <span>Hi\u1EC7n t\u1EA1i {{ hw.actual }}cm</span>\n <span>Trung v\u1ECB WHO {{ hw.median }}cm</span>\n </div>\n <div class=\"ds-growth__scale\" aria-hidden=\"true\">\n <div class=\"ds-growth__scale-labels\">\n <span>Th\u1EA5p</span>\n <span class=\"ds-growth__scale-mid\">Ph\u00F9 h\u1EE3p</span>\n <span>Cao</span>\n </div>\n <div class=\"ds-growth__scale-track\">\n <span class=\"ds-growth__band ds-growth__band--low\"></span>\n <span class=\"ds-growth__band ds-growth__band--mid\"></span>\n <span class=\"ds-growth__band ds-growth__band--high\"></span>\n <span\n class=\"ds-growth__marker\"\n [attr.data-status]=\"hw.status\"\n [style.left.%]=\"whoMarkerPosition(hw.zScore)\"\n ></span>\n </div>\n <div class=\"ds-growth__scale-legend\">\n <span>\u22122 SD</span>\n <span>P{{ hw.percentile }} \u00B7 z {{ formatWhoZ(hw.zScore) }}</span>\n <span>+2 SD</span>\n </div>\n </div>\n <div class=\"ds-growth__delta\" [attr.data-status]=\"hw.status\">\n {{ hw.deltaLabel }}\n </div>\n } @else {\n <div class=\"ds-growth__empty\">\n {{ s.body.heightCm ? 'Thi\u1EBFu ng\u00E0y sinh/gi\u1EDBi t\u00EDnh \u0111\u1EC3 so chu\u1EA9n' : 'Ch\u01B0a \u0111o chi\u1EC1u cao' }}\n </div>\n }\n </article>\n </div>\n\n <div class=\"ds-body-card__meta\">\n @if (s.body.daysFromViewedDay === 0) {\n C\u00E2n \u0111\u00FAng ng\u00E0y n\u00E0y\n } @else if (s.body.measuredAfter) {\n C\u00E2n ng\u00E0y {{ s.body.measuredDisplay }}, sau ng\u00E0y n\u00E0y {{ s.body.daysFromViewedDay }} ng\u00E0y\n } @else {\n C\u00E2n g\u1EA7n nh\u1EA5t tr\u01B0\u1EDBc \u0111\u00F3, ng\u00E0y {{ s.body.measuredDisplay }}\n }\n @if (s.body.heightDate && s.body.heightDate !== s.body.measuredDate) {\n \u00B7 \u0111o chi\u1EC1u cao ng\u00E0y {{ s.body.heightDisplay }}\n }\n </div>\n\n @if (s.nutrition) {\n <div class=\"ds-target\">\n <div class=\"ds-target__head\">\n <span class=\"ds-target__label\">L\u01B0\u1EE3ng s\u1EEFa so khuy\u1EBFn ngh\u1ECB</span>\n <span\n class=\"ds-body-card__percent\"\n [class.ds-body-card__percent--low]=\"s.nutrition.percentOfMin < 85\"\n [class.ds-body-card__percent--ok]=\"s.nutrition.percentOfMin >= 85\"\n >\n {{ s.nutrition.percentOfMin }}%\n </span>\n </div>\n <div class=\"ds-target__track\">\n <span\n class=\"ds-target__fill\"\n [class.ds-target__fill--low]=\"s.nutrition.percentOfMin < 85\"\n [style.width.%]=\"s.nutrition.barPercent\"\n ></span>\n </div>\n <div class=\"ds-target__scale\">\n <span>0 ml</span>\n <span>\n Khuy\u1EBFn ngh\u1ECB {{ s.nutrition.dailyMlMin }}\u2013{{ s.nutrition.dailyMlMax }} ml m\u1ED7i ng\u00E0y\n </span>\n </div>\n <div class=\"ds-target__note\">\n @if (s.nutrition.remainingToMin > 0) {\n B\u00E9 \u0111\u00E3 b\u00FA {{ s.totalMl }}ml, c\u00F2n thi\u1EBFu {{ s.nutrition.remainingToMin }}ml n\u1EEFa m\u1EDBi \u0111\u1EA1t\n m\u1EE9c t\u1ED1i thi\u1EC3u.\n } @else {\n {{ s.nutrition.statusLabel }} \u2014 b\u00E9 \u0111\u00E3 b\u00FA {{ s.totalMl }}ml trong ng\u00E0y.\n }\n </div>\n </div>\n } @else {\n <div class=\"ds-body-card__meta\">\n Ch\u01B0a t\u00EDnh \u0111\u01B0\u1EE3c khuy\u1EBFn ngh\u1ECB l\u01B0\u1EE3ng s\u1EEFa v\u00EC thi\u1EBFu ng\u00E0y sinh c\u1EE7a b\u00E9.\n </div>\n }\n </section>\n }\n\n <!-- Nh\u00ECn l\u1EA1i -->\n @if (s.recent.length > 0) {\n <section aria-label=\"Nh\u00ECn l\u1EA1i v\u00E0i ng\u00E0y tr\u01B0\u1EDBc\">\n <div class=\"ds-head ds-head--pink\">\n <span class=\"ds-head__bar\"></span>\n <span class=\"ds-head__label\">Nh\u00ECn l\u1EA1i v\u00E0i ng\u00E0y tr\u01B0\u1EDBc</span>\n <span class=\"ds-head__count\">{{ s.recent.length }}</span>\n <span class=\"ds-head__spacer\"></span>\n <span class=\"ds-head__action\">Y t\u1EBF {{ lookBackDays }} ng\u00E0y g\u1EA7n \u0111\u00E2y</span>\n </div>\n <div class=\"ds-list\">\n @for (item of s.recent; track item.date + item.title) {\n <button\n type=\"button\"\n class=\"ds-related\"\n [style.border-left-color]=\"item.color\"\n (click)=\"openDate(item.date)\"\n >\n <span class=\"ds-related__badge\" [style.color]=\"item.color\">\n <span class=\"ds-related__days\">{{ item.days }}</span>\n <span class=\"ds-related__unit\">ng\u00E0y tr\u01B0\u1EDBc</span>\n </span>\n <span class=\"ds-related__body\">\n <span class=\"ds-related__title\">{{ item.title }}</span>\n <span class=\"ds-related__meta\"> {{ item.weekday }} ng\u00E0y {{ item.displayDate }} </span>\n </span>\n <i class=\"pi pi-chevron-right ds-related__chev\" aria-hidden=\"true\"></i>\n </button>\n }\n </div>\n </section>\n }\n\n <!-- C\u1EEF b\u00FA b\u1ECB d\u00E3n -->\n @if (s.stretchedGaps.length > 0) {\n <section aria-label=\"C\u1EEF b\u00FA b\u1ECB d\u00E3n\">\n <div class=\"ds-head ds-head--orange\">\n <span class=\"ds-head__bar\"></span>\n <span class=\"ds-head__label\">C\u1EEF b\u00FA b\u1ECB d\u00E3n</span>\n <span class=\"ds-head__count\">{{ s.stretchedGaps.length }} l\u1EA7n</span>\n <span class=\"ds-head__spacer\"></span>\n <span class=\"ds-head__action\">Ng\u01B0\u1EE1ng {{ s.stretchThresholdHuman }}</span>\n </div>\n <div class=\"ds-list\">\n @for (gap of s.stretchedGaps; track gap.from + gap.to) {\n <div class=\"ds-gap\">\n <div class=\"ds-gap__row\">\n <span class=\"ds-gap__time\">{{ gap.from }}</span>\n <span class=\"ds-gap__track\">\n <span class=\"ds-gap__fill\" [style.width.%]=\"gap.barPercent\"></span>\n <span class=\"ds-gap__value\">{{ gap.humanGap }}</span>\n </span>\n <span class=\"ds-gap__time\">{{ gap.to }}</span>\n </div>\n <div class=\"ds-gap__foot\">\n <span class=\"ds-gap__note\">\n {{ gap.note || 'Kho\u1EA3ng c\u00E1ch d\u00E0i h\u01A1n nh\u1ECBp b\u00FA th\u01B0\u1EDDng ng\u00E0y' }}\n </span>\n <span class=\"ds-gap__volume\">{{ gap.volume }}ml</span>\n </div>\n </div>\n }\n <div class=\"ds-hint\">\n Kho\u1EA3ng c\u00E1ch trung b\u00ECnh h\u00F4m nay {{ s.avgGapHuman }}, d\u00E0i nh\u1EA5t {{ s.maxGapHuman }}\n </div>\n </div>\n </section>\n }\n\n <!-- C\u1EEF b\u00FA -->\n <section aria-label=\"C\u1EEF b\u00FA\">\n <div class=\"ds-head ds-head--cyan\">\n <span class=\"ds-head__bar\"></span>\n <span class=\"ds-head__label\">C\u1EEF b\u00FA</span>\n @if (s.feedCount > 0) {\n <span class=\"ds-head__count\">{{ s.feedCount }} c\u1EEF b\u00FA</span>\n }\n </div>\n @if (!s.hasFeeding) {\n <div class=\"ds-empty\">Kh\u00F4ng c\u00F3 c\u1EEF b\u00FA n\u00E0o \u0111\u01B0\u1EE3c ghi nh\u1EADn trong ng\u00E0y n\u00E0y.</div>\n } @else {\n <div class=\"ds-feed-periods\">\n @for (period of s.feedPeriods; track period.id) {\n <div class=\"ds-period\" [attr.aria-label]=\"period.label\">\n <div class=\"ds-period__head\">\n <span class=\"ds-period__label\">{{ period.label }}</span>\n <span class=\"ds-period__meta\">\n {{ period.feedCount }} c\u1EEF \u00B7 {{ period.totalMl }}ml\n </span>\n </div>\n <div class=\"ds-feeds\">\n @for (feed of period.feeds; track feed.time + feed.volume + $index) {\n <div class=\"ds-feed-block\">\n <div class=\"ds-feed\">\n <span\n class=\"ds-feed__time\"\n [class.ds-feed__time--night]=\"feed.night\"\n [class.ds-feed__time--low]=\"feed.level === 'under' || feed.level === 'low'\"\n [class.ds-feed__time--high]=\"feed.level === 'high'\"\n >\n {{ feed.time }}\n </span>\n <span class=\"ds-feed__track\">\n <span\n class=\"ds-feed__fill\"\n [class.ds-feed__fill--night]=\"feed.night && feed.level === 'normal'\"\n [class.ds-feed__fill--under]=\"feed.level === 'under'\"\n [class.ds-feed__fill--low]=\"feed.level === 'low'\"\n [class.ds-feed__fill--high]=\"feed.level === 'high'\"\n [style.width.%]=\"feed.barPercent\"\n ></span>\n </span>\n <span class=\"ds-feed__volume\">{{ feed.volume }}ml</span>\n <span class=\"ds-feed__flag\">\n @if (feed.level === 'under' || feed.level === 'low') {\n <i\n class=\"pi pi-arrow-down ds-feed__flag--low\"\n [attr.title]=\"feed.levelLabel\"\n [attr.aria-label]=\"feed.levelLabel\"\n ></i>\n } @else if (feed.level === 'high') {\n <i\n class=\"pi pi-arrow-up ds-feed__flag--high\"\n [attr.title]=\"feed.levelLabel\"\n [attr.aria-label]=\"feed.levelLabel\"\n ></i>\n }\n </span>\n </div>\n @if (feed.memberCount > 1) {\n <div class=\"ds-feed__children\">\n @for (m of feed.members; track m.time + m.volume) {\n <div class=\"ds-feed ds-feed--child\">\n <span\n class=\"ds-feed__time\"\n [class.ds-feed__time--night]=\"m.night\"\n >\n {{ m.time }}\n </span>\n <span class=\"ds-feed__track\">\n <span\n class=\"ds-feed__fill\"\n [class.ds-feed__fill--night]=\"m.night\"\n [style.width.%]=\"m.barPercent\"\n ></span>\n </span>\n <span class=\"ds-feed__volume\">{{ m.volume }}ml</span>\n <span class=\"ds-feed__flag\" aria-hidden=\"true\"></span>\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n </div>\n }\n </div>\n @if (lowFeedCount() > 0) {\n <div class=\"ds-hint ds-hint--amber\">\n {{ lowFeedCount() }} c\u1EEF \u00EDt h\u01A1n m\u1EE9c g\u1EE3i \u00FD\n @if (s.nutrition) {\n kho\u1EA3ng {{ s.nutrition.expectedPerFeed }}ml m\u1ED7i c\u1EEF v\u1EDBi {{ s.feedCount }} c\u1EEF trong ng\u00E0y\n } @else {\n so v\u1EDBi nh\u1ECBp b\u00FA chung c\u1EE7a ng\u00E0y\n }\n </div>\n }\n }\n </section>\n\n <!-- Ho\u1EA1t \u0111\u1ED9ng -->\n <section aria-label=\"Ho\u1EA1t \u0111\u1ED9ng\">\n <div class=\"ds-head ds-head--purple\">\n <span class=\"ds-head__bar\"></span>\n <span class=\"ds-head__label\">Ho\u1EA1t \u0111\u1ED9ng</span>\n @if (s.activities.length > 0) {\n <span class=\"ds-head__count\">{{ s.activities.length }}</span>\n }\n </div>\n @if (loadingMedical()) {\n <div class=\"ds-empty\">\u0110ang t\u1EA3i ho\u1EA1t \u0111\u1ED9ng y t\u1EBF\u2026</div>\n } @else if (s.activities.length === 0) {\n <div class=\"ds-empty\">Kh\u00F4ng c\u00F3 ho\u1EA1t \u0111\u1ED9ng n\u00E0o trong ng\u00E0y n\u00E0y.</div>\n } @else {\n <div class=\"ds-list\">\n @for (act of s.activities; track act.title) {\n <div class=\"ds-card\" [style.border-left-color]=\"act.kindColor\">\n <div class=\"ds-card__top\">\n <span\n class=\"ds-tag\"\n [style.color]=\"act.kindColor\"\n [style.border-color]=\"act.kindColor\"\n >\n <i class=\"fa-solid\" [ngClass]=\"act.kindIcon\" aria-hidden=\"true\"></i>\n {{ act.kindLabel }}\n </span>\n @if (act.place) {\n <span class=\"ds-card__place\">{{ act.place }}</span>\n }\n </div>\n <div class=\"ds-card__title\">{{ act.title }}</div>\n @if (act.detail) {\n <div class=\"ds-card__detail\">{{ act.detail }}</div>\n }\n </div>\n }\n </div>\n }\n </section>\n\n <!-- L\u1ECBch -->\n <section aria-label=\"L\u1ECBch\">\n <div class=\"ds-head ds-head--blue\">\n <span class=\"ds-head__bar\"></span>\n <span class=\"ds-head__label\">L\u1ECBch</span>\n @if (s.events.length > 0) {\n <span class=\"ds-head__count\">{{ s.events.length }}</span>\n }\n </div>\n @if (s.events.length === 0) {\n <div class=\"ds-empty\">Kh\u00F4ng c\u00F3 l\u1ECBch h\u1EB9n n\u00E0o trong ng\u00E0y n\u00E0y.</div>\n } @else {\n <div class=\"ds-list\">\n @for (ev of s.events; track ev.time + ev.title) {\n <div class=\"ds-event\">\n <span class=\"ds-event__time\">{{ ev.time }}</span>\n <span class=\"ds-event__body\">\n <span class=\"ds-event__title\">{{ ev.title }}</span>\n @if (ev.place) {\n <span class=\"ds-event__meta\">{{ ev.place }}</span>\n }\n @if (ev.note) {\n <span class=\"ds-event__meta\">{{ ev.note }}</span>\n }\n </span>\n </div>\n }\n </div>\n }\n </section>\n\n <!-- S\u1EAFp t\u1EDBi -->\n <section aria-label=\"S\u1EAFp t\u1EDBi\">\n <div class=\"ds-head ds-head--green\">\n <span class=\"ds-head__bar\"></span>\n <span class=\"ds-head__label\">S\u1EAFp t\u1EDBi</span>\n @if (s.upcoming.length > 0) {\n <span class=\"ds-head__count\">{{ s.upcoming.length }}</span>\n }\n <span class=\"ds-head__spacer\"></span>\n <span class=\"ds-head__action\">{{ lookAheadDays }} ng\u00E0y t\u1EDBi</span>\n </div>\n @if (s.upcoming.length === 0) {\n <div class=\"ds-empty\">Ch\u01B0a c\u00F3 l\u1ECBch h\u1EB9n n\u00E0o trong {{ lookAheadDays }} ng\u00E0y t\u1EDBi.</div>\n } @else {\n <div class=\"ds-list\">\n @for (item of s.upcoming; track item.date + item.title) {\n <div class=\"ds-related ds-related--static\" [style.border-left-color]=\"item.color\">\n <span class=\"ds-related__badge\" [style.color]=\"item.color\">\n <span class=\"ds-related__days\">{{ item.days }}</span>\n <span class=\"ds-related__unit\">ng\u00E0y n\u1EEFa</span>\n </span>\n <span class=\"ds-related__body\">\n <span class=\"ds-related__title\">{{ item.title }}</span>\n <span class=\"ds-related__meta\"> {{ item.weekday }} ng\u00E0y {{ item.displayDate }} </span>\n </span>\n </div>\n }\n </div>\n }\n </section>\n</div>\n", styles: ["@charset \"UTF-8\";:host{--bg: #f4f6fb;--surface: #ffffff;--border: #dfe5ef;--text: #0b1220;--muted: #5a6a85;--faint: #8c9ab1;--cyan: #0891b2;--cyan-soft: #dff7fd;--cyan-line: #7fdcee;--indigo: #5b5bf0;--indigo-soft: #e9e9ff;--indigo-line: #b4b4fb;--orange: #ef7314;--orange-soft: #fff0e2;--orange-line: #fbc088;--amber: #b45309;--amber-strong: #f59e0b;--amber-soft: #fef6e0;--amber-line: #f6cf7d;--purple: #8b3ff5;--blue: #2563eb;--green: #16a34a;--pink: #e0338c;--r-card: 10px;--r-small: 6px;display:block;background:var(--bg);color:var(--text)}.ds{display:flex;flex-direction:column;gap:18px;padding:14px 14px 32px}.ds-datebar{display:flex;align-items:center;justify-content:space-between;gap:8px;background:var(--cyan-soft);border:1px solid var(--cyan-line);border-radius:var(--r-card);padding:8px 10px}.ds-datebar--weekend{background:var(--indigo-soft);border-color:var(--indigo-line)}.ds-datebar--weekend .ds-datebar__nav{color:var(--indigo)}.ds-datebar--weekend .ds-datebar__nav:disabled{color:var(--faint)}.ds-datebar--weekend .ds-datebar__label{color:var(--indigo)}.ds-datebar__nav{border:none;background:transparent;color:var(--cyan);font-size:15px;padding:6px 10px;cursor:pointer}.ds-datebar__nav:disabled{color:var(--faint);cursor:default}.ds-datebar__center{position:relative;flex:1;min-width:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;padding:4px 8px;cursor:pointer;border-radius:var(--r-small)}.ds-datebar__label{font-size:15px;font-weight:800;color:var(--cyan);text-align:center;line-height:1.25}.ds-datebar__badge{font-size:10px;font-weight:800;letter-spacing:.02em;text-transform:uppercase;color:var(--indigo);background:var(--surface);border:1px solid var(--indigo-line);border-radius:999px;padding:1px 7px}.ds-datebar__input{position:absolute;inset:0;width:100%;height:100%;opacity:0;border:none;cursor:pointer}.ds-reset{align-self:center;margin-top:-12px;display:inline-flex;align-items:center;gap:6px;border:1px solid var(--cyan-line);background:var(--surface);color:var(--cyan);border-radius:999px;padding:6px 14px;font-family:inherit;font-size:12px;font-weight:700;cursor:pointer}.ds-reset i{font-size:12px}.ds-datebar--weekend~.ds-reset{border-color:var(--indigo-line);color:var(--indigo)}.ds-hero{background:var(--cyan-soft);border:1px solid var(--cyan-line);border-radius:var(--r-card);padding:14px;color:var(--text)}.ds-hero__top{display:flex;align-items:flex-end;justify-content:space-between;gap:12px}.ds-hero__label{font-size:11px;font-weight:800;color:var(--cyan)}.ds-hero__total{font-size:38px;font-weight:800;line-height:1.05;margin-top:2px;color:var(--cyan)}.ds-hero__unit{font-size:15px;font-weight:700;margin-left:4px}.ds-hero__delta{display:inline-flex;align-items:center;flex-wrap:wrap;gap:4px;margin-top:6px;font-size:12px;font-weight:700;color:var(--muted)}.ds-hero__delta i{font-size:11px}.ds-hero__delta--up{color:#047857}.ds-hero__delta--down{color:#b45309}.ds-hero__delta--flat{color:var(--muted)}.ds-hero__delta-pct{font-weight:800}.ds-hero__right{text-align:right}.ds-hero__count{font-size:18px;font-weight:800}.ds-hero__avg{font-size:11px;font-weight:600;color:var(--muted)}.ds-hero__stats{display:flex;gap:8px;margin-top:14px}.ds-hero__stat{flex:1;background:var(--surface);border:1px solid var(--cyan-line);border-radius:var(--r-small);padding:8px 10px}.ds-hero__stat-label{display:block;font-size:10px;font-weight:700;color:var(--faint)}.ds-hero__stat-value{display:block;font-size:14px;font-weight:800;margin-top:2px;color:var(--text)}.ds-hero__stat-value--green{color:var(--green)}.ds-hero__stat-value--blue{color:var(--blue)}.ds-hero__stat-value--indigo{color:var(--indigo)}.ds-body-card{background:var(--surface);border:1px solid var(--border);border-left:4px solid var(--green);border-radius:var(--r-card);padding:12px 14px}.ds-body-card__title{font-size:12px;font-weight:800;letter-spacing:.02em;color:var(--muted);margin-bottom:10px}.ds-growth{display:grid;grid-template-columns:1fr 1fr;gap:10px}@media(max-width:420px){.ds-growth{grid-template-columns:1fr}}.ds-growth__card{background:var(--bg);border:1px solid var(--border);border-radius:var(--r-card);padding:10px 11px 11px;min-width:0}.ds-growth__card[data-status=normal]{border-color:#86efac;background:#f0fdf4}.ds-growth__card[data-status=low],.ds-growth__card[data-status=very_low]{border-color:var(--amber-line);background:var(--amber-soft)}.ds-growth__card[data-status=high],.ds-growth__card[data-status=very_high]{border-color:#93c5fd;background:#eff6ff}.ds-growth__head{display:flex;align-items:center;justify-content:space-between;gap:6px;margin-bottom:4px}.ds-growth__label{display:inline-flex;align-items:center;gap:5px;font-size:11px;font-weight:800;color:var(--muted)}.ds-growth__label i{font-size:11px;color:var(--green)}.ds-growth__badge{font-size:10px;font-weight:800;border-radius:999px;padding:2px 7px;white-space:nowrap;background:var(--surface);border:1px solid var(--border);color:var(--muted)}.ds-growth__badge[data-status=normal]{color:var(--green);border-color:#86efac}.ds-growth__badge[data-status=low],.ds-growth__badge[data-status=very_low]{color:var(--amber);border-color:var(--amber-line)}.ds-growth__badge[data-status=high],.ds-growth__badge[data-status=very_high]{color:var(--blue);border-color:#93c5fd}.ds-growth__value{font-size:24px;font-weight:800;line-height:1.1;font-variant-numeric:tabular-nums}.ds-growth__value span{font-size:12px;font-weight:700;margin-left:3px;color:var(--muted)}.ds-growth__compare{display:flex;flex-direction:column;gap:1px;margin-top:6px;font-size:10px;font-weight:600;color:var(--faint)}.ds-growth__scale{margin-top:8px}.ds-growth__scale-labels{display:flex;justify-content:space-between;font-size:9px;font-weight:700;color:var(--faint);margin-bottom:3px}.ds-growth__scale-mid{color:var(--green)}.ds-growth__scale-track{position:relative;display:flex;height:10px;border-radius:5px;overflow:hidden;background:var(--border)}.ds-growth__band{display:block;height:100%}.ds-growth__band--low{width:25%;background:#fcd34d}.ds-growth__band--mid{width:50%;background:#86efac}.ds-growth__band--high{width:25%;background:#93c5fd}.ds-growth__marker{position:absolute;top:50%;width:12px;height:12px;margin-left:-6px;margin-top:-6px;border-radius:50%;background:var(--text);border:2px solid #ffffff;box-sizing:border-box}.ds-growth__marker[data-status=normal]{background:var(--green)}.ds-growth__marker[data-status=low],.ds-growth__marker[data-status=very_low]{background:var(--amber)}.ds-growth__marker[data-status=high],.ds-growth__marker[data-status=very_high]{background:var(--blue)}.ds-growth__scale-legend{display:flex;justify-content:space-between;margin-top:3px;font-size:9px;font-weight:700;color:var(--faint);font-variant-numeric:tabular-nums}.ds-growth__delta{margin-top:7px;font-size:11px;font-weight:700;color:var(--green)}.ds-growth__delta[data-status=low],.ds-growth__delta[data-status=very_low]{color:var(--amber)}.ds-growth__delta[data-status=high],.ds-growth__delta[data-status=very_high]{color:var(--blue)}.ds-growth__delta[data-status=unknown]{color:var(--muted)}.ds-growth__empty{margin-top:8px;font-size:11px;font-weight:600;color:var(--faint);line-height:1.4}.ds-body-card__percent{font-size:18px;font-weight:800;color:var(--green)}.ds-body-card__percent--low{color:var(--amber)}.ds-body-card__meta{font-size:11px;color:var(--faint);margin-top:8px}.ds-target{margin-top:10px;padding-top:10px;border-top:1px solid var(--border)}.ds-target__head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:6px}.ds-target__label{font-size:11px;font-weight:800;color:var(--muted)}.ds-target__track{height:10px;background:var(--bg);border-radius:3px;overflow:hidden}.ds-target__fill{display:block;height:100%;background:var(--green)}.ds-target__fill--low{background:var(--amber-strong)}.ds-target__scale{display:flex;justify-content:space-between;gap:10px;font-size:10px;color:var(--faint);margin-top:4px}.ds-target__note{font-size:12px;color:var(--muted);margin-top:6px}.ds-night{background:var(--indigo-soft);border:1px solid var(--indigo-line);border-left:4px solid var(--indigo);border-radius:var(--r-card);padding:10px 12px;font-size:12px;font-weight:700;color:var(--indigo)}.ds-strip{background:var(--surface);border:1px solid var(--border);border-radius:var(--r-card);padding:10px 6px 8px}.ds-strip__frame{display:flex;align-items:stretch;gap:2px}.ds-strip__nav{flex-shrink:0;align-self:center;width:26px;height:52px;border:1px solid var(--border);border-radius:8px;background:#f8fafc;color:var(--cyan);font-size:12px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;padding:0}.ds-strip__nav:disabled{color:var(--faint);cursor:default;opacity:.45}.ds-strip__content{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}.ds-strip__chart{display:flex;gap:1px;align-items:flex-end;height:56px;padding:0 0 2px}.ds-strip__bar-col{flex:1;min-width:0;display:flex;flex-direction:column;align-items:center;justify-content:flex-end;gap:2px;height:100%}.ds-strip__bar-track{flex:1;width:100%;max-width:18px;display:flex;align-items:flex-end;justify-content:center}.ds-strip__bar{width:100%;min-height:3px;border-radius:4px 4px 2px 2px;background:linear-gradient(180deg,#22d3ee,var(--cyan));transition:height .2s ease}.ds-strip__bar--empty{min-height:2px;height:2px!important;background:#e2e8f0;opacity:.7}.ds-strip__bar-col--current .ds-strip__bar:not(.ds-strip__bar--empty){background:linear-gradient(180deg,#34d399,#059669)}.ds-strip__bar-col--weekend .ds-strip__bar:not(.ds-strip__bar--empty){background:linear-gradient(180deg,#a5b4fc,var(--indigo))}.ds-strip__bar-col--current.ds-strip__bar-col--weekend .ds-strip__bar:not(.ds-strip__bar--empty){background:linear-gradient(180deg,#818cf8,#4f46e5)}.ds-strip__bar-label{font-size:8px;font-weight:800;color:var(--muted);line-height:1;font-variant-numeric:tabular-nums}.ds-strip__bar-col--current .ds-strip__bar-label{color:var(--cyan)}.ds-strip__row{display:flex;gap:1px}.ds-strip__cell{flex:1;min-width:0;border:none;background:transparent;padding:2px 0 0;cursor:pointer;display:flex;flex-direction:column;align-items:center;gap:3px;font-family:inherit}.ds-strip__cell:disabled{cursor:default;opacity:.4}.ds-strip__dow{font-size:9px;font-weight:700;color:var(--faint)}.ds-strip__cell--weekend .ds-strip__dow{color:var(--indigo)}.ds-strip__day{width:22px;height:22px;border-radius:11px;display:inline-flex;align-items:center;justify-content:center;font-size:11px;font-weight:800;color:var(--faint);border:1px solid transparent}.ds-strip__day--data{color:var(--text);border-color:var(--border)}.ds-strip__cell--current .ds-strip__dow{color:var(--cyan)}.ds-strip__cell--current .ds-strip__day{background:var(--cyan);border-color:var(--cyan);color:#fff}.ds-strip__cell--current.ds-strip__cell--weekend .ds-strip__dow{color:var(--indigo)}.ds-strip__cell--current.ds-strip__cell--weekend .ds-strip__day{background:var(--indigo);border-color:var(--indigo)}.ds-strip__dot{width:4px;height:4px;border-radius:2px;background:transparent;display:inline-block}.ds-strip__dot--activity{background:var(--purple)}.ds-strip__dot--event{background:var(--blue)}.ds-strip__legend{display:flex;gap:12px;margin-top:6px;padding-left:2px;font-size:10px;color:var(--muted)}.ds-strip__legend span{display:inline-flex;align-items:center;gap:5px}.ds-head{display:flex;align-items:center;gap:8px;margin-bottom:8px;--tone: var(--cyan)}.ds-head--cyan{--tone: var(--cyan)}.ds-head--orange{--tone: var(--orange)}.ds-head--purple{--tone: var(--purple)}.ds-head--blue{--tone: var(--blue)}.ds-head--green{--tone: var(--green)}.ds-head--pink{--tone: var(--pink)}.ds-head__bar{width:4px;height:16px;border-radius:1px;background:var(--tone)}.ds-head__label{font-size:14px;font-weight:700}.ds-head__count{font-size:11px;font-weight:700;color:#fff;background:var(--tone);border-radius:var(--r-small);padding:2px 7px}.ds-head__spacer{flex:1}.ds-head__action{font-size:11px;font-weight:700;color:var(--tone)}.ds-list{display:flex;flex-direction:column;gap:8px}.ds-empty{background:var(--surface);border:1px dashed var(--border);border-radius:var(--r-card);padding:12px 14px;font-size:12px;color:var(--faint)}.ds-hint{font-size:11px;color:var(--faint)}.ds-hint--amber{color:var(--amber);font-weight:700;margin-top:8px}.ds-tag{display:inline-flex;align-items:center;gap:5px;font-size:11px;font-weight:700;border:1px solid var(--border);border-radius:var(--r-small);padding:2px 8px;white-space:nowrap}.ds-tag--indigo{color:var(--indigo);background:var(--indigo-soft);border-color:var(--indigo-line)}.ds-tag--amber{color:var(--amber);background:var(--amber-soft);border-color:var(--amber-line)}.ds-tag--cyan{color:var(--cyan);background:var(--cyan-soft);border-color:var(--cyan-line)}.ds-gap{background:var(--surface);border:1px solid var(--orange-line);border-left:4px solid var(--orange);border-radius:var(--r-card);padding:11px 14px}.ds-gap__row{display:flex;align-items:center;gap:10px}.ds-gap__time{font-size:13px;font-weight:700;font-variant-numeric:tabular-nums}.ds-gap__track{position:relative;flex:1;height:20px;display:block}.ds-gap__track:before{content:\"\";position:absolute;top:9px;left:0;right:0;height:3px;background:var(--orange-soft)}.ds-gap__fill{position:absolute;top:9px;left:0;height:3px;background:var(--orange)}.ds-gap__value{position:absolute;top:-1px;left:50%;transform:translate(-50%);font-size:11px;font-weight:800;color:#fff;background:var(--orange);border-radius:var(--r-small);padding:2px 8px;white-space:nowrap}.ds-gap__foot{display:flex;justify-content:space-between;align-items:center;gap:10px;margin-top:8px}.ds-gap__note{font-size:12px;color:var(--muted)}.ds-gap__volume{font-size:12px;font-weight:700;white-space:nowrap}.ds-feed-periods{display:flex;flex-direction:column;gap:14px}.ds-period__head{display:flex;align-items:baseline;justify-content:space-between;gap:10px;margin-bottom:6px;padding:0 2px}.ds-period__label{font-size:12px;font-weight:800;letter-spacing:.04em;text-transform:uppercase;color:var(--cyan)}.ds-period__meta{font-size:11px;font-weight:600;color:var(--faint)}.ds-feeds{background:var(--surface);border:1px solid var(--border);border-radius:var(--r-card);padding:12px;display:flex;flex-direction:column;gap:10px}.ds-feed-block{display:flex;flex-direction:column;gap:5px}.ds-feed{display:grid;grid-template-columns:42px minmax(0,1fr) 52px 22px;align-items:center;column-gap:10px}.ds-feed--child{grid-template-columns:42px minmax(0,1fr) 52px 22px;opacity:.92}.ds-feed__children{display:flex;flex-direction:column;gap:5px;padding-left:10px;border-left:2px solid var(--cyan-line);margin-left:6px}.ds-feed__time{width:42px;font-size:12px;font-weight:700;font-variant-numeric:tabular-nums}.ds-feed__time--night{color:var(--indigo)}.ds-feed__time--low{color:var(--amber)}.ds-feed__time--high{color:var(--green)}.ds-feed--child .ds-feed__time{font-weight:600;color:var(--muted)}.ds-feed--child .ds-feed__time.ds-feed__time--night{color:var(--indigo)}.ds-feed__track{height:12px;background:var(--bg);border-radius:3px;overflow:hidden;display:block;min-width:0}.ds-feed--child .ds-feed__track{height:8px}.ds-feed__fill{display:block;height:100%;background:var(--cyan)}.ds-feed__fill--night{background:var(--indigo)}.ds-feed__fill--under{background:var(--amber-strong)}.ds-feed__fill--low{background:var(--amber-strong);opacity:.75}.ds-feed__fill--high{background:var(--green)}.ds-feed__volume{width:52px;text-align:right;font-size:12px;font-weight:700;font-variant-numeric:tabular-nums;white-space:nowrap}.ds-feed--child .ds-feed__volume{font-weight:600;color:var(--muted)}.ds-feed__flag{width:22px;height:18px;display:inline-flex;align-items:center;justify-content:center}.ds-feed__flag .pi{font-size:12px;font-weight:700}.ds-feed__flag--low{color:var(--amber)}.ds-feed__flag--high{color:var(--green)}.ds-card{background:var(--surface);border:1px solid var(--border);border-left:4px solid var(--purple);border-radius:var(--r-card);padding:11px 13px}.ds-card__top{display:flex;align-items:center;gap:8px;margin-bottom:6px}.ds-card__place{font-size:11px;color:var(--faint)}.ds-card__title{font-size:14px;font-weight:700}.ds-card__detail{font-size:12px;color:var(--muted);margin-top:3px}.ds-event{display:flex;gap:12px;align-items:center;background:var(--surface);border:1px solid var(--border);border-left:4px solid var(--blue);border-radius:var(--r-card);padding:11px 13px}.ds-event__time{background:var(--blue);color:#fff;border-radius:var(--r-small);padding:6px 9px;font-size:13px;font-weight:800;white-space:nowrap}.ds-event__title{display:block;font-size:14px;font-weight:700}.ds-event__meta{display:block;font-size:12px;color:var(--muted);margin-top:2px}.ds-related{display:flex;align-items:center;gap:12px;width:100%;text-align:left;background:var(--surface);border:1px solid var(--border);border-left:4px solid var(--pink);border-radius:var(--r-card);padding:10px 12px;cursor:pointer;color:var(--text);font-family:inherit}.ds-related--static{cursor:default}.ds-related__badge{min-width:62px;text-align:center;border:1px solid currentcolor;border-radius:var(--r-small);padding:5px 4px}.ds-related__days{display:block;font-size:15px;font-weight:800}.ds-related__unit{display:block;font-size:10px;font-weight:700}.ds-related__body{flex:1;min-width:0}.ds-related__title{display:block;font-size:13px;font-weight:700}.ds-related__meta{display:block;font-size:11px;color:var(--muted);margin-top:2px}.ds-related__chev{font-size:12px;color:var(--faint)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
21311
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: DailySummaryComponent, isStandalone: true, selector: "app-daily-summary", inputs: { logs: { classPropertyName: "logs", publicName: "logs", isSignal: true, isRequired: false, transformFunction: null }, birthDate: { classPropertyName: "birthDate", publicName: "birthDate", isSignal: true, isRequired: false, transformFunction: null }, initialDate: { classPropertyName: "initialDate", publicName: "initialDate", isSignal: true, isRequired: false, transformFunction: null }, feedGroupGapMinutes: { classPropertyName: "feedGroupGapMinutes", publicName: "feedGroupGapMinutes", isSignal: true, isRequired: false, transformFunction: null }, gender: { classPropertyName: "gender", publicName: "gender", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@let s = summary();\n\n<div class=\"ds\">\n <!-- \u0110i\u1EC1u h\u01B0\u1EDBng ng\u00E0y -->\n <div class=\"ds-datebar\" [class.ds-datebar--weekend]=\"s.weekend\">\n <button type=\"button\" class=\"ds-datebar__nav\" (click)=\"shiftDay(-1)\" aria-label=\"Ng\u00E0y tr\u01B0\u1EDBc\">\n <i class=\"pi pi-chevron-left\" aria-hidden=\"true\"></i>\n </button>\n\n <label class=\"ds-datebar__center\">\n <span class=\"ds-datebar__label\">\n {{ s.weekday }}, {{ s.displayDate }}\n </span>\n @if (s.weekend) {\n <span class=\"ds-datebar__badge\">Cu\u1ED1i tu\u1EA7n</span>\n }\n <input\n type=\"date\"\n class=\"ds-datebar__input\"\n [value]=\"s.date\"\n [max]=\"maxDate\"\n (change)=\"onDateInput($any($event.target).value)\"\n aria-label=\"Ch\u1ECDn ng\u00E0y\"\n />\n </label>\n\n <button\n type=\"button\"\n class=\"ds-datebar__nav\"\n [disabled]=\"!canGoForward()\"\n (click)=\"shiftDay(1)\"\n aria-label=\"Ng\u00E0y sau\"\n >\n <i class=\"pi pi-chevron-right\" aria-hidden=\"true\"></i>\n </button>\n </div>\n\n @if (canGoForward()) {\n <button type=\"button\" class=\"ds-reset\" (click)=\"goLatest()\">\n <i class=\"pi pi-replay\" aria-hidden=\"true\"></i>\n V\u1EC1 ng\u00E0y m\u1EDBi nh\u1EA5t ({{ maxDateLabel }})\n </button>\n }\n\n <!-- T\u1ED5ng quan -->\n <section class=\"ds-hero\" aria-label=\"T\u1ED5ng quan trong ng\u00E0y\">\n <div class=\"ds-hero__top\">\n <div>\n <div class=\"ds-hero__label\">T\u1ED4NG L\u01AF\u1EE2NG S\u1EEEA TRONG NG\u00C0Y</div>\n <div class=\"ds-hero__total\">{{ s.totalMl }}<span class=\"ds-hero__unit\">ml</span></div>\n @if (s.hasFeeding || s.yesterday.totalMl > 0) {\n <div\n class=\"ds-hero__delta\"\n [class.ds-hero__delta--up]=\"s.yesterday.deltaMl > 0\"\n [class.ds-hero__delta--down]=\"s.yesterday.deltaMl < 0\"\n [class.ds-hero__delta--flat]=\"s.yesterday.deltaMl === 0\"\n >\n @if (s.yesterday.totalMl === 0 && s.hasFeeding) {\n H\u00F4m qua ch\u01B0a ghi nh\u1EADn c\u1EEF\n } @else if (s.yesterday.deltaMl === 0) {\n B\u1EB1ng h\u00F4m qua ({{ s.yesterday.totalMl }}ml)\n } @else {\n <i\n class=\"pi\"\n [class.pi-arrow-up]=\"s.yesterday.deltaMl > 0\"\n [class.pi-arrow-down]=\"s.yesterday.deltaMl < 0\"\n aria-hidden=\"true\"\n ></i>\n {{ s.yesterday.deltaMl > 0 ? '+' : '' }}{{ s.yesterday.deltaMl }}ml\n @if (s.yesterday.deltaPercent !== null) {\n <span class=\"ds-hero__delta-pct\">\n ({{ s.yesterday.deltaPercent > 0 ? '+' : '' }}{{ s.yesterday.deltaPercent }}%)\n </span>\n }\n so v\u1EDBi h\u00F4m qua\n }\n </div>\n }\n </div>\n <div class=\"ds-hero__right\">\n <div class=\"ds-hero__count\">{{ s.feedCount }} c\u1EEF b\u00FA</div>\n <div class=\"ds-hero__avg\">\n @if (s.feedCount > 0) {\n trung b\u00ECnh {{ s.avgMl }}ml m\u1ED7i c\u1EEF\n } @else {\n ch\u01B0a ghi nh\u1EADn c\u1EEF n\u00E0o\n }\n </div>\n </div>\n </div>\n\n <div class=\"ds-hero__stats\">\n <div class=\"ds-hero__stat\">\n <span class=\"ds-hero__stat-label\">C\u1EEF cao nh\u1EA5t</span>\n <span class=\"ds-hero__stat-value ds-hero__stat-value--green\">\n {{ s.hasFeeding ? s.maxMl + 'ml' : '\u2014' }}\n </span>\n </div>\n <div class=\"ds-hero__stat\">\n <span class=\"ds-hero__stat-label\">C\u1EEF th\u1EA5p nh\u1EA5t</span>\n <span class=\"ds-hero__stat-value ds-hero__stat-value--blue\">\n {{ s.hasFeeding ? s.minMl + 'ml' : '\u2014' }}\n </span>\n </div>\n <div class=\"ds-hero__stat\">\n <span class=\"ds-hero__stat-label\">B\u00FA \u0111\u00EAm</span>\n <span\n class=\"ds-hero__stat-value\"\n [class.ds-hero__stat-value--indigo]=\"s.nightCount > 0\"\n [class.ds-hero__stat-value--green]=\"s.nightCount === 0\"\n >\n {{ s.nightCount > 0 ? s.nightCount + ' c\u1EEF' : 'Kh\u00F4ng c\u00F3' }}\n </span>\n </div>\n </div>\n </section>\n\n @if (s.nightCount > 0) {\n <div class=\"ds-night\">\n B\u00E9 c\u00F3 b\u00FA \u0111\u00EAm trong khung 00:00 \u2013 05:00: {{ s.nightCount }} c\u1EEF b\u00FA, t\u1ED5ng {{ s.nightMl }}ml\n </div>\n }\n\n <!-- D\u1EA3i hai tu\u1EA7n -->\n <section class=\"ds-strip\" aria-label=\"D\u1EA3i hai tu\u1EA7n\">\n <div class=\"ds-strip__frame\">\n <button\n type=\"button\"\n class=\"ds-strip__nav\"\n (click)=\"shiftStripWeek(-2)\"\n aria-label=\"Tu\u1EA7n tr\u01B0\u1EDBc\"\n >\n <i class=\"pi pi-chevron-left\" aria-hidden=\"true\"></i>\n </button>\n\n <div class=\"ds-strip__content\">\n <div class=\"ds-strip__chart\" aria-hidden=\"true\">\n @for (cell of s.strip; track cell.date) {\n <div\n class=\"ds-strip__bar-col\"\n [class.ds-strip__bar-col--current]=\"cell.current\"\n [class.ds-strip__bar-col--weekend]=\"cell.weekend\"\n >\n <div class=\"ds-strip__bar-track\">\n <span\n class=\"ds-strip__bar\"\n [class.ds-strip__bar--empty]=\"!cell.hasFeeding\"\n [style.height.%]=\"cell.barPercent\"\n ></span>\n </div>\n @if (cell.hasFeeding) {\n <span class=\"ds-strip__bar-label\">{{ cell.totalMl }}</span>\n }\n </div>\n }\n </div>\n\n <div class=\"ds-strip__row\">\n @for (cell of s.strip; track cell.date) {\n <button\n type=\"button\"\n class=\"ds-strip__cell\"\n [class.ds-strip__cell--current]=\"cell.current\"\n [class.ds-strip__cell--weekend]=\"cell.weekend\"\n [disabled]=\"!isNavigable(cell.date)\"\n (click)=\"openDate(cell.date)\"\n [attr.aria-current]=\"cell.current ? 'date' : null\"\n [attr.aria-label]=\"cell.weekdayShort + ' ng\u00E0y ' + cell.dayNumber\"\n >\n <span class=\"ds-strip__dow\">{{ cell.weekdayShort }}</span>\n <span class=\"ds-strip__day\" [class.ds-strip__day--data]=\"cell.hasFeeding\">\n {{ cell.dayNumber }}\n </span>\n <span\n class=\"ds-strip__dot\"\n [class.ds-strip__dot--activity]=\"cell.hasActivity\"\n [class.ds-strip__dot--event]=\"!cell.hasActivity && cell.hasEvent\"\n ></span>\n </button>\n }\n </div>\n </div>\n\n <button\n type=\"button\"\n class=\"ds-strip__nav\"\n [disabled]=\"!canShiftStripForward()\"\n (click)=\"shiftStripWeek(2)\"\n aria-label=\"Tu\u1EA7n sau\"\n >\n <i class=\"pi pi-chevron-right\" aria-hidden=\"true\"></i>\n </button>\n </div>\n\n <div class=\"ds-strip__legend\">\n <span><i class=\"ds-strip__dot ds-strip__dot--activity\"></i> C\u00F3 ho\u1EA1t \u0111\u1ED9ng</span>\n <span><i class=\"ds-strip__dot ds-strip__dot--event\"></i> C\u00F3 l\u1ECBch h\u1EB9n</span>\n </div>\n </section>\n\n <!-- Th\u1EC3 tr\u1EA1ng & m\u1EE9c \u0111\u1EA1t khuy\u1EBFn ngh\u1ECB -->\n @if (s.body) {\n <section class=\"ds-body-card\" aria-label=\"Th\u1EC3 tr\u1EA1ng v\u00E0 khuy\u1EBFn ngh\u1ECB\">\n <div class=\"ds-body-card__title\">Th\u1EC3 tr\u1EA1ng & khuy\u1EBFn ngh\u1ECB</div>\n\n <div class=\"ds-growth\">\n <article\n class=\"ds-growth__card\"\n [attr.data-status]=\"s.body.weightWho?.status ?? 'unknown'\"\n >\n <div class=\"ds-growth__head\">\n <span class=\"ds-growth__label\">\n <i class=\"pi pi-heart-fill\" aria-hidden=\"true\"></i>\n C\u00E2n n\u1EB7ng\n </span>\n @if (s.body.weightWho; as ww) {\n <span class=\"ds-growth__badge\" [attr.data-status]=\"ww.status\">\n {{ ww.badgeLabel }}\n </span>\n }\n </div>\n <div class=\"ds-growth__value\">{{ s.body.weightKg }}<span>kg</span></div>\n\n @if (s.body.weightWho; as ww) {\n <div class=\"ds-growth__compare\">\n <span>Hi\u1EC7n t\u1EA1i {{ ww.actual }}kg</span>\n <span>Trung v\u1ECB WHO {{ ww.median }}kg</span>\n </div>\n <div class=\"ds-growth__scale\" aria-hidden=\"true\">\n <div class=\"ds-growth__scale-labels\">\n <span>Th\u1EA5p</span>\n <span class=\"ds-growth__scale-mid\">Ph\u00F9 h\u1EE3p</span>\n <span>Cao</span>\n </div>\n <div class=\"ds-growth__scale-track\">\n <span class=\"ds-growth__band ds-growth__band--low\"></span>\n <span class=\"ds-growth__band ds-growth__band--mid\"></span>\n <span class=\"ds-growth__band ds-growth__band--high\"></span>\n <span\n class=\"ds-growth__marker\"\n [attr.data-status]=\"ww.status\"\n [style.left.%]=\"whoMarkerPosition(ww.zScore)\"\n ></span>\n </div>\n <div class=\"ds-growth__scale-legend\">\n <span>\u22122 SD</span>\n <span>P{{ ww.percentile }} \u00B7 z {{ formatWhoZ(ww.zScore) }}</span>\n <span>+2 SD</span>\n </div>\n </div>\n <div class=\"ds-growth__delta\" [attr.data-status]=\"ww.status\">\n {{ ww.deltaLabel }}\n </div>\n } @else {\n <div class=\"ds-growth__empty\">\n @if (s.body.ageInDays !== null) {\n Th\u00EAm gi\u1EDBi t\u00EDnh trong profile \u0111\u1EC3 so chu\u1EA9n WHO\n } @else {\n C\u1EA7n ng\u00E0y sinh \u0111\u1EC3 so chu\u1EA9n WHO\n }\n </div>\n }\n </article>\n\n <article\n class=\"ds-growth__card\"\n [attr.data-status]=\"s.body.heightWho?.status ?? 'unknown'\"\n >\n <div class=\"ds-growth__head\">\n <span class=\"ds-growth__label\">\n <i class=\"pi pi-arrows-v\" aria-hidden=\"true\"></i>\n Chi\u1EC1u cao\n </span>\n @if (s.body.heightWho; as hw) {\n <span class=\"ds-growth__badge\" [attr.data-status]=\"hw.status\">\n {{ hw.badgeLabel }}\n </span>\n }\n </div>\n <div class=\"ds-growth__value\">\n @if (s.body.heightCm) {\n {{ s.body.heightCm }}<span>cm</span>\n } @else {\n \u2014<span>cm</span>\n }\n </div>\n\n @if (s.body.heightWho; as hw) {\n <div class=\"ds-growth__compare\">\n <span>Hi\u1EC7n t\u1EA1i {{ hw.actual }}cm</span>\n <span>Trung v\u1ECB WHO {{ hw.median }}cm</span>\n </div>\n <div class=\"ds-growth__scale\" aria-hidden=\"true\">\n <div class=\"ds-growth__scale-labels\">\n <span>Th\u1EA5p</span>\n <span class=\"ds-growth__scale-mid\">Ph\u00F9 h\u1EE3p</span>\n <span>Cao</span>\n </div>\n <div class=\"ds-growth__scale-track\">\n <span class=\"ds-growth__band ds-growth__band--low\"></span>\n <span class=\"ds-growth__band ds-growth__band--mid\"></span>\n <span class=\"ds-growth__band ds-growth__band--high\"></span>\n <span\n class=\"ds-growth__marker\"\n [attr.data-status]=\"hw.status\"\n [style.left.%]=\"whoMarkerPosition(hw.zScore)\"\n ></span>\n </div>\n <div class=\"ds-growth__scale-legend\">\n <span>\u22122 SD</span>\n <span>P{{ hw.percentile }} \u00B7 z {{ formatWhoZ(hw.zScore) }}</span>\n <span>+2 SD</span>\n </div>\n </div>\n <div class=\"ds-growth__delta\" [attr.data-status]=\"hw.status\">\n {{ hw.deltaLabel }}\n </div>\n } @else {\n <div class=\"ds-growth__empty\">\n {{ s.body.heightCm ? 'Thi\u1EBFu ng\u00E0y sinh/gi\u1EDBi t\u00EDnh \u0111\u1EC3 so chu\u1EA9n' : 'Ch\u01B0a \u0111o chi\u1EC1u cao' }}\n </div>\n }\n </article>\n </div>\n\n <div class=\"ds-body-card__meta\">\n @if (s.body.daysFromViewedDay === 0) {\n C\u00E2n \u0111\u00FAng ng\u00E0y n\u00E0y\n } @else if (s.body.measuredAfter) {\n C\u00E2n ng\u00E0y {{ s.body.measuredDisplay }}, sau ng\u00E0y n\u00E0y {{ s.body.daysFromViewedDay }} ng\u00E0y\n } @else {\n C\u00E2n g\u1EA7n nh\u1EA5t tr\u01B0\u1EDBc \u0111\u00F3, ng\u00E0y {{ s.body.measuredDisplay }}\n }\n @if (s.body.heightDate && s.body.heightDate !== s.body.measuredDate) {\n \u00B7 \u0111o chi\u1EC1u cao ng\u00E0y {{ s.body.heightDisplay }}\n }\n </div>\n\n @if (s.nutrition) {\n <div class=\"ds-target\">\n <div class=\"ds-target__head\">\n <span class=\"ds-target__label\">L\u01B0\u1EE3ng s\u1EEFa so khuy\u1EBFn ngh\u1ECB</span>\n <span\n class=\"ds-body-card__percent\"\n [class.ds-body-card__percent--low]=\"s.nutrition.percentOfMin < 85\"\n [class.ds-body-card__percent--ok]=\"s.nutrition.percentOfMin >= 85\"\n >\n {{ s.nutrition.percentOfMin }}%\n </span>\n </div>\n <div class=\"ds-target__track\">\n <span\n class=\"ds-target__fill\"\n [class.ds-target__fill--low]=\"s.nutrition.percentOfMin < 85\"\n [style.width.%]=\"s.nutrition.barPercent\"\n ></span>\n </div>\n <div class=\"ds-target__scale\">\n <span>0 ml</span>\n <span>\n Khuy\u1EBFn ngh\u1ECB {{ s.nutrition.dailyMlMin }}\u2013{{ s.nutrition.dailyMlMax }} ml m\u1ED7i ng\u00E0y\n </span>\n </div>\n <div class=\"ds-target__note\">\n @if (s.nutrition.remainingToMin > 0) {\n B\u00E9 \u0111\u00E3 b\u00FA {{ s.totalMl }}ml, c\u00F2n thi\u1EBFu {{ s.nutrition.remainingToMin }}ml n\u1EEFa m\u1EDBi \u0111\u1EA1t\n m\u1EE9c t\u1ED1i thi\u1EC3u.\n } @else {\n {{ s.nutrition.statusLabel }} \u2014 b\u00E9 \u0111\u00E3 b\u00FA {{ s.totalMl }}ml trong ng\u00E0y.\n }\n </div>\n </div>\n } @else {\n <div class=\"ds-body-card__meta\">\n Ch\u01B0a t\u00EDnh \u0111\u01B0\u1EE3c khuy\u1EBFn ngh\u1ECB l\u01B0\u1EE3ng s\u1EEFa v\u00EC thi\u1EBFu ng\u00E0y sinh c\u1EE7a b\u00E9.\n </div>\n }\n </section>\n }\n\n <!-- Nh\u00ECn l\u1EA1i -->\n @if (s.recent.length > 0) {\n <section aria-label=\"Nh\u00ECn l\u1EA1i v\u00E0i ng\u00E0y tr\u01B0\u1EDBc\">\n <div class=\"ds-head ds-head--pink\">\n <span class=\"ds-head__bar\"></span>\n <span class=\"ds-head__label\">Nh\u00ECn l\u1EA1i v\u00E0i ng\u00E0y tr\u01B0\u1EDBc</span>\n <span class=\"ds-head__count\">{{ s.recent.length }}</span>\n <span class=\"ds-head__spacer\"></span>\n <span class=\"ds-head__action\">Y t\u1EBF {{ lookBackDays }} ng\u00E0y g\u1EA7n \u0111\u00E2y</span>\n </div>\n <div class=\"ds-list\">\n @for (item of s.recent; track item.date + item.title) {\n <button\n type=\"button\"\n class=\"ds-related\"\n [style.border-left-color]=\"item.color\"\n (click)=\"openDate(item.date)\"\n >\n <span class=\"ds-related__badge\" [style.color]=\"item.color\">\n <span class=\"ds-related__days\">{{ item.days }}</span>\n <span class=\"ds-related__unit\">ng\u00E0y tr\u01B0\u1EDBc</span>\n </span>\n <span class=\"ds-related__body\">\n <span class=\"ds-related__title\">{{ item.title }}</span>\n <span class=\"ds-related__meta\"> {{ item.weekday }} ng\u00E0y {{ item.displayDate }} </span>\n </span>\n <i class=\"pi pi-chevron-right ds-related__chev\" aria-hidden=\"true\"></i>\n </button>\n }\n </div>\n </section>\n }\n\n <!-- C\u1EEF b\u00FA b\u1ECB d\u00E3n -->\n @if (s.stretchedGaps.length > 0) {\n <section aria-label=\"C\u1EEF b\u00FA b\u1ECB d\u00E3n\">\n <div class=\"ds-head ds-head--orange\">\n <span class=\"ds-head__bar\"></span>\n <span class=\"ds-head__label\">C\u1EEF b\u00FA b\u1ECB d\u00E3n</span>\n <span class=\"ds-head__count\">{{ s.stretchedGaps.length }} l\u1EA7n</span>\n <span class=\"ds-head__spacer\"></span>\n <span class=\"ds-head__action\">Ng\u01B0\u1EE1ng {{ s.stretchThresholdHuman }}</span>\n </div>\n <div class=\"ds-list\">\n @for (gap of s.stretchedGaps; track gap.from + gap.to) {\n <div class=\"ds-gap\">\n <div class=\"ds-gap__row\">\n <span class=\"ds-gap__time\">{{ gap.from }}</span>\n <span class=\"ds-gap__track\">\n <span class=\"ds-gap__fill\" [style.width.%]=\"gap.barPercent\"></span>\n <span class=\"ds-gap__value\">{{ gap.humanGap }}</span>\n </span>\n <span class=\"ds-gap__time\">{{ gap.to }}</span>\n </div>\n <div class=\"ds-gap__foot\">\n <span class=\"ds-gap__note\">\n {{ gap.note || 'Kho\u1EA3ng c\u00E1ch d\u00E0i h\u01A1n nh\u1ECBp b\u00FA th\u01B0\u1EDDng ng\u00E0y' }}\n </span>\n <span class=\"ds-gap__volume\">{{ gap.volume }}ml</span>\n </div>\n </div>\n }\n <div class=\"ds-hint\">\n Kho\u1EA3ng c\u00E1ch trung b\u00ECnh h\u00F4m nay {{ s.avgGapHuman }}, d\u00E0i nh\u1EA5t {{ s.maxGapHuman }}\n </div>\n </div>\n </section>\n }\n\n <!-- C\u1EEF b\u00FA -->\n <section aria-label=\"C\u1EEF b\u00FA\">\n <div class=\"ds-head ds-head--cyan\">\n <span class=\"ds-head__bar\"></span>\n <span class=\"ds-head__label\">C\u1EEF b\u00FA</span>\n @if (s.feedCount > 0) {\n <span class=\"ds-head__count\">{{ s.feedCount }} c\u1EEF b\u00FA</span>\n }\n </div>\n @if (!s.hasFeeding) {\n <div class=\"ds-empty\">Kh\u00F4ng c\u00F3 c\u1EEF b\u00FA n\u00E0o \u0111\u01B0\u1EE3c ghi nh\u1EADn trong ng\u00E0y n\u00E0y.</div>\n } @else {\n <div class=\"ds-feed-periods\">\n @for (period of s.feedPeriods; track period.id) {\n <div class=\"ds-period\" [attr.aria-label]=\"period.label\">\n <div class=\"ds-period__head\">\n <span class=\"ds-period__label\">{{ period.label }}</span>\n <span class=\"ds-period__meta\">\n {{ period.feedCount }} c\u1EEF \u00B7 {{ period.totalMl }}ml\n </span>\n </div>\n <div class=\"ds-feeds\">\n @for (feed of period.feeds; track feed.time + feed.volume + $index) {\n <div class=\"ds-feed-block\">\n <div class=\"ds-feed\">\n <span\n class=\"ds-feed__time\"\n [class.ds-feed__time--night]=\"feed.night\"\n [class.ds-feed__time--low]=\"feed.level === 'under' || feed.level === 'low'\"\n [class.ds-feed__time--high]=\"feed.level === 'high'\"\n >\n {{ feed.time }}\n </span>\n <span class=\"ds-feed__track\">\n <span\n class=\"ds-feed__fill\"\n [class.ds-feed__fill--night]=\"feed.night && feed.level === 'normal'\"\n [class.ds-feed__fill--under]=\"feed.level === 'under'\"\n [class.ds-feed__fill--low]=\"feed.level === 'low'\"\n [class.ds-feed__fill--high]=\"feed.level === 'high'\"\n [style.width.%]=\"feed.barPercent\"\n ></span>\n </span>\n <span class=\"ds-feed__volume\">{{ feed.volume }}ml</span>\n <span class=\"ds-feed__flag\">\n @if (feed.level === 'under' || feed.level === 'low') {\n <i\n class=\"pi pi-arrow-down ds-feed__flag--low\"\n [attr.title]=\"feed.levelLabel\"\n [attr.aria-label]=\"feed.levelLabel\"\n ></i>\n } @else if (feed.level === 'high') {\n <i\n class=\"pi pi-arrow-up ds-feed__flag--high\"\n [attr.title]=\"feed.levelLabel\"\n [attr.aria-label]=\"feed.levelLabel\"\n ></i>\n }\n </span>\n </div>\n @if (feed.memberCount > 1) {\n <div class=\"ds-feed__children\">\n @for (m of feed.members; track m.time + m.volume) {\n <div class=\"ds-feed ds-feed--child\">\n <span\n class=\"ds-feed__time\"\n [class.ds-feed__time--night]=\"m.night\"\n >\n {{ m.time }}\n </span>\n <span class=\"ds-feed__track\">\n <span\n class=\"ds-feed__fill\"\n [class.ds-feed__fill--night]=\"m.night\"\n [style.width.%]=\"m.barPercent\"\n ></span>\n </span>\n <span class=\"ds-feed__volume\">{{ m.volume }}ml</span>\n <span class=\"ds-feed__flag\" aria-hidden=\"true\"></span>\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n </div>\n }\n </div>\n @if (lowFeedCount() > 0) {\n <div class=\"ds-hint ds-hint--amber\">\n {{ lowFeedCount() }} c\u1EEF \u00EDt h\u01A1n m\u1EE9c g\u1EE3i \u00FD\n @if (s.nutrition) {\n kho\u1EA3ng {{ s.nutrition.expectedPerFeed }}ml m\u1ED7i c\u1EEF v\u1EDBi {{ s.feedCount }} c\u1EEF trong ng\u00E0y\n } @else {\n so v\u1EDBi nh\u1ECBp b\u00FA chung c\u1EE7a ng\u00E0y\n }\n </div>\n }\n }\n </section>\n\n <!-- Ho\u1EA1t \u0111\u1ED9ng -->\n <section aria-label=\"Ho\u1EA1t \u0111\u1ED9ng\">\n <div class=\"ds-head ds-head--purple\">\n <span class=\"ds-head__bar\"></span>\n <span class=\"ds-head__label\">Ho\u1EA1t \u0111\u1ED9ng</span>\n @if (s.activities.length > 0) {\n <span class=\"ds-head__count\">{{ s.activities.length }}</span>\n }\n </div>\n @if (loadingMedical()) {\n <div class=\"ds-empty\">\u0110ang t\u1EA3i ho\u1EA1t \u0111\u1ED9ng y t\u1EBF\u2026</div>\n } @else if (s.activities.length === 0) {\n <div class=\"ds-empty\">Kh\u00F4ng c\u00F3 ho\u1EA1t \u0111\u1ED9ng n\u00E0o trong ng\u00E0y n\u00E0y.</div>\n } @else {\n <div class=\"ds-list\">\n @for (act of s.activities; track act.title) {\n <div class=\"ds-card\" [style.border-left-color]=\"act.kindColor\">\n <div class=\"ds-card__top\">\n <span\n class=\"ds-tag\"\n [style.color]=\"act.kindColor\"\n [style.border-color]=\"act.kindColor\"\n >\n <i class=\"fa-solid\" [ngClass]=\"act.kindIcon\" aria-hidden=\"true\"></i>\n {{ act.kindLabel }}\n </span>\n @if (act.place) {\n <span class=\"ds-card__place\">{{ act.place }}</span>\n }\n </div>\n <div class=\"ds-card__title\">{{ act.title }}</div>\n @if (act.detail) {\n <div class=\"ds-card__detail\" [innerHTML]=\"act.detail | safeRichHtml\"></div>\n }\n </div>\n }\n </div>\n }\n </section>\n\n <!-- L\u1ECBch -->\n <section aria-label=\"L\u1ECBch\">\n <div class=\"ds-head ds-head--blue\">\n <span class=\"ds-head__bar\"></span>\n <span class=\"ds-head__label\">L\u1ECBch</span>\n @if (s.events.length > 0) {\n <span class=\"ds-head__count\">{{ s.events.length }}</span>\n }\n </div>\n @if (s.events.length === 0) {\n <div class=\"ds-empty\">Kh\u00F4ng c\u00F3 l\u1ECBch h\u1EB9n n\u00E0o trong ng\u00E0y n\u00E0y.</div>\n } @else {\n <div class=\"ds-list\">\n @for (ev of s.events; track ev.time + ev.title) {\n <div class=\"ds-event\">\n <span class=\"ds-event__time\">{{ ev.time }}</span>\n <span class=\"ds-event__body\">\n <span class=\"ds-event__title\">{{ ev.title }}</span>\n @if (ev.place) {\n <span class=\"ds-event__meta\">{{ ev.place }}</span>\n }\n @if (ev.note) {\n <span class=\"ds-event__meta\">{{ ev.note }}</span>\n }\n </span>\n </div>\n }\n </div>\n }\n </section>\n\n <!-- S\u1EAFp t\u1EDBi -->\n <section aria-label=\"S\u1EAFp t\u1EDBi\">\n <div class=\"ds-head ds-head--green\">\n <span class=\"ds-head__bar\"></span>\n <span class=\"ds-head__label\">S\u1EAFp t\u1EDBi</span>\n @if (s.upcoming.length > 0) {\n <span class=\"ds-head__count\">{{ s.upcoming.length }}</span>\n }\n <span class=\"ds-head__spacer\"></span>\n <span class=\"ds-head__action\">{{ lookAheadDays }} ng\u00E0y t\u1EDBi</span>\n </div>\n @if (s.upcoming.length === 0) {\n <div class=\"ds-empty\">Ch\u01B0a c\u00F3 l\u1ECBch h\u1EB9n n\u00E0o trong {{ lookAheadDays }} ng\u00E0y t\u1EDBi.</div>\n } @else {\n <div class=\"ds-list\">\n @for (item of s.upcoming; track item.date + item.title) {\n <div class=\"ds-related ds-related--static\" [style.border-left-color]=\"item.color\">\n <span class=\"ds-related__badge\" [style.color]=\"item.color\">\n <span class=\"ds-related__days\">{{ item.days }}</span>\n <span class=\"ds-related__unit\">ng\u00E0y n\u1EEFa</span>\n </span>\n <span class=\"ds-related__body\">\n <span class=\"ds-related__title\">{{ item.title }}</span>\n <span class=\"ds-related__meta\"> {{ item.weekday }} ng\u00E0y {{ item.displayDate }} </span>\n </span>\n </div>\n }\n </div>\n }\n </section>\n</div>\n", styles: ["@charset \"UTF-8\";:host{--bg: #f4f6fb;--surface: #ffffff;--border: #dfe5ef;--text: #0b1220;--muted: #5a6a85;--faint: #8c9ab1;--cyan: #0891b2;--cyan-soft: #dff7fd;--cyan-line: #7fdcee;--indigo: #5b5bf0;--indigo-soft: #e9e9ff;--indigo-line: #b4b4fb;--orange: #ef7314;--orange-soft: #fff0e2;--orange-line: #fbc088;--amber: #b45309;--amber-strong: #f59e0b;--amber-soft: #fef6e0;--amber-line: #f6cf7d;--purple: #8b3ff5;--blue: #2563eb;--green: #16a34a;--pink: #e0338c;--r-card: 10px;--r-small: 6px;display:block;background:var(--bg);color:var(--text)}.ds{display:flex;flex-direction:column;gap:18px;padding:14px 14px 32px}.ds-datebar{display:flex;align-items:center;justify-content:space-between;gap:8px;background:var(--cyan-soft);border:1px solid var(--cyan-line);border-radius:var(--r-card);padding:8px 10px}.ds-datebar--weekend{background:var(--indigo-soft);border-color:var(--indigo-line)}.ds-datebar--weekend .ds-datebar__nav{color:var(--indigo)}.ds-datebar--weekend .ds-datebar__nav:disabled{color:var(--faint)}.ds-datebar--weekend .ds-datebar__label{color:var(--indigo)}.ds-datebar__nav{border:none;background:transparent;color:var(--cyan);font-size:15px;padding:6px 10px;cursor:pointer}.ds-datebar__nav:disabled{color:var(--faint);cursor:default}.ds-datebar__center{position:relative;flex:1;min-width:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;padding:4px 8px;cursor:pointer;border-radius:var(--r-small)}.ds-datebar__label{font-size:15px;font-weight:800;color:var(--cyan);text-align:center;line-height:1.25}.ds-datebar__badge{font-size:10px;font-weight:800;letter-spacing:.02em;text-transform:uppercase;color:var(--indigo);background:var(--surface);border:1px solid var(--indigo-line);border-radius:999px;padding:1px 7px}.ds-datebar__input{position:absolute;inset:0;width:100%;height:100%;opacity:0;border:none;cursor:pointer}.ds-reset{align-self:center;margin-top:-12px;display:inline-flex;align-items:center;gap:6px;border:1px solid var(--cyan-line);background:var(--surface);color:var(--cyan);border-radius:999px;padding:6px 14px;font-family:inherit;font-size:12px;font-weight:700;cursor:pointer}.ds-reset i{font-size:12px}.ds-datebar--weekend~.ds-reset{border-color:var(--indigo-line);color:var(--indigo)}.ds-hero{background:var(--cyan-soft);border:1px solid var(--cyan-line);border-radius:var(--r-card);padding:14px;color:var(--text)}.ds-hero__top{display:flex;align-items:flex-end;justify-content:space-between;gap:12px}.ds-hero__label{font-size:11px;font-weight:800;color:var(--cyan)}.ds-hero__total{font-size:38px;font-weight:800;line-height:1.05;margin-top:2px;color:var(--cyan)}.ds-hero__unit{font-size:15px;font-weight:700;margin-left:4px}.ds-hero__delta{display:inline-flex;align-items:center;flex-wrap:wrap;gap:4px;margin-top:6px;font-size:12px;font-weight:700;color:var(--muted)}.ds-hero__delta i{font-size:11px}.ds-hero__delta--up{color:#047857}.ds-hero__delta--down{color:#b45309}.ds-hero__delta--flat{color:var(--muted)}.ds-hero__delta-pct{font-weight:800}.ds-hero__right{text-align:right}.ds-hero__count{font-size:18px;font-weight:800}.ds-hero__avg{font-size:11px;font-weight:600;color:var(--muted)}.ds-hero__stats{display:flex;gap:8px;margin-top:14px}.ds-hero__stat{flex:1;background:var(--surface);border:1px solid var(--cyan-line);border-radius:var(--r-small);padding:8px 10px}.ds-hero__stat-label{display:block;font-size:10px;font-weight:700;color:var(--faint)}.ds-hero__stat-value{display:block;font-size:14px;font-weight:800;margin-top:2px;color:var(--text)}.ds-hero__stat-value--green{color:var(--green)}.ds-hero__stat-value--blue{color:var(--blue)}.ds-hero__stat-value--indigo{color:var(--indigo)}.ds-body-card{background:var(--surface);border:1px solid var(--border);border-left:4px solid var(--green);border-radius:var(--r-card);padding:12px 14px}.ds-body-card__title{font-size:12px;font-weight:800;letter-spacing:.02em;color:var(--muted);margin-bottom:10px}.ds-growth{display:grid;grid-template-columns:1fr 1fr;gap:10px}@media(max-width:420px){.ds-growth{grid-template-columns:1fr}}.ds-growth__card{background:var(--bg);border:1px solid var(--border);border-radius:var(--r-card);padding:10px 11px 11px;min-width:0}.ds-growth__card[data-status=normal]{border-color:#86efac;background:#f0fdf4}.ds-growth__card[data-status=low],.ds-growth__card[data-status=very_low]{border-color:var(--amber-line);background:var(--amber-soft)}.ds-growth__card[data-status=high],.ds-growth__card[data-status=very_high]{border-color:#93c5fd;background:#eff6ff}.ds-growth__head{display:flex;align-items:center;justify-content:space-between;gap:6px;margin-bottom:4px}.ds-growth__label{display:inline-flex;align-items:center;gap:5px;font-size:11px;font-weight:800;color:var(--muted)}.ds-growth__label i{font-size:11px;color:var(--green)}.ds-growth__badge{font-size:10px;font-weight:800;border-radius:999px;padding:2px 7px;white-space:nowrap;background:var(--surface);border:1px solid var(--border);color:var(--muted)}.ds-growth__badge[data-status=normal]{color:var(--green);border-color:#86efac}.ds-growth__badge[data-status=low],.ds-growth__badge[data-status=very_low]{color:var(--amber);border-color:var(--amber-line)}.ds-growth__badge[data-status=high],.ds-growth__badge[data-status=very_high]{color:var(--blue);border-color:#93c5fd}.ds-growth__value{font-size:24px;font-weight:800;line-height:1.1;font-variant-numeric:tabular-nums}.ds-growth__value span{font-size:12px;font-weight:700;margin-left:3px;color:var(--muted)}.ds-growth__compare{display:flex;flex-direction:column;gap:1px;margin-top:6px;font-size:10px;font-weight:600;color:var(--faint)}.ds-growth__scale{margin-top:8px}.ds-growth__scale-labels{display:flex;justify-content:space-between;font-size:9px;font-weight:700;color:var(--faint);margin-bottom:3px}.ds-growth__scale-mid{color:var(--green)}.ds-growth__scale-track{position:relative;display:flex;height:10px;border-radius:5px;overflow:hidden;background:var(--border)}.ds-growth__band{display:block;height:100%}.ds-growth__band--low{width:25%;background:#fcd34d}.ds-growth__band--mid{width:50%;background:#86efac}.ds-growth__band--high{width:25%;background:#93c5fd}.ds-growth__marker{position:absolute;top:50%;width:12px;height:12px;margin-left:-6px;margin-top:-6px;border-radius:50%;background:var(--text);border:2px solid #ffffff;box-sizing:border-box}.ds-growth__marker[data-status=normal]{background:var(--green)}.ds-growth__marker[data-status=low],.ds-growth__marker[data-status=very_low]{background:var(--amber)}.ds-growth__marker[data-status=high],.ds-growth__marker[data-status=very_high]{background:var(--blue)}.ds-growth__scale-legend{display:flex;justify-content:space-between;margin-top:3px;font-size:9px;font-weight:700;color:var(--faint);font-variant-numeric:tabular-nums}.ds-growth__delta{margin-top:7px;font-size:11px;font-weight:700;color:var(--green)}.ds-growth__delta[data-status=low],.ds-growth__delta[data-status=very_low]{color:var(--amber)}.ds-growth__delta[data-status=high],.ds-growth__delta[data-status=very_high]{color:var(--blue)}.ds-growth__delta[data-status=unknown]{color:var(--muted)}.ds-growth__empty{margin-top:8px;font-size:11px;font-weight:600;color:var(--faint);line-height:1.4}.ds-body-card__percent{font-size:18px;font-weight:800;color:var(--green)}.ds-body-card__percent--low{color:var(--amber)}.ds-body-card__meta{font-size:11px;color:var(--faint);margin-top:8px}.ds-target{margin-top:10px;padding-top:10px;border-top:1px solid var(--border)}.ds-target__head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:6px}.ds-target__label{font-size:11px;font-weight:800;color:var(--muted)}.ds-target__track{height:10px;background:var(--bg);border-radius:3px;overflow:hidden}.ds-target__fill{display:block;height:100%;background:var(--green)}.ds-target__fill--low{background:var(--amber-strong)}.ds-target__scale{display:flex;justify-content:space-between;gap:10px;font-size:10px;color:var(--faint);margin-top:4px}.ds-target__note{font-size:12px;color:var(--muted);margin-top:6px}.ds-night{background:var(--indigo-soft);border:1px solid var(--indigo-line);border-left:4px solid var(--indigo);border-radius:var(--r-card);padding:10px 12px;font-size:12px;font-weight:700;color:var(--indigo)}.ds-strip{background:var(--surface);border:1px solid var(--border);border-radius:var(--r-card);padding:10px 6px 8px}.ds-strip__frame{display:flex;align-items:stretch;gap:2px}.ds-strip__nav{flex-shrink:0;align-self:center;width:26px;height:52px;border:1px solid var(--border);border-radius:8px;background:#f8fafc;color:var(--cyan);font-size:12px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;padding:0}.ds-strip__nav:disabled{color:var(--faint);cursor:default;opacity:.45}.ds-strip__content{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}.ds-strip__chart{display:flex;gap:1px;align-items:flex-end;height:56px;padding:0 0 2px}.ds-strip__bar-col{flex:1;min-width:0;display:flex;flex-direction:column;align-items:center;justify-content:flex-end;gap:2px;height:100%}.ds-strip__bar-track{flex:1;width:100%;max-width:18px;display:flex;align-items:flex-end;justify-content:center}.ds-strip__bar{width:100%;min-height:3px;border-radius:4px 4px 2px 2px;background:linear-gradient(180deg,#22d3ee,var(--cyan));transition:height .2s ease}.ds-strip__bar--empty{min-height:2px;height:2px!important;background:#e2e8f0;opacity:.7}.ds-strip__bar-col--current .ds-strip__bar:not(.ds-strip__bar--empty){background:linear-gradient(180deg,#34d399,#059669)}.ds-strip__bar-col--weekend .ds-strip__bar:not(.ds-strip__bar--empty){background:linear-gradient(180deg,#a5b4fc,var(--indigo))}.ds-strip__bar-col--current.ds-strip__bar-col--weekend .ds-strip__bar:not(.ds-strip__bar--empty){background:linear-gradient(180deg,#818cf8,#4f46e5)}.ds-strip__bar-label{font-size:8px;font-weight:800;color:var(--muted);line-height:1;font-variant-numeric:tabular-nums}.ds-strip__bar-col--current .ds-strip__bar-label{color:var(--cyan)}.ds-strip__row{display:flex;gap:1px}.ds-strip__cell{flex:1;min-width:0;border:none;background:transparent;padding:2px 0 0;cursor:pointer;display:flex;flex-direction:column;align-items:center;gap:3px;font-family:inherit}.ds-strip__cell:disabled{cursor:default;opacity:.4}.ds-strip__dow{font-size:9px;font-weight:700;color:var(--faint)}.ds-strip__cell--weekend .ds-strip__dow{color:var(--indigo)}.ds-strip__day{width:22px;height:22px;border-radius:11px;display:inline-flex;align-items:center;justify-content:center;font-size:11px;font-weight:800;color:var(--faint);border:1px solid transparent}.ds-strip__day--data{color:var(--text);border-color:var(--border)}.ds-strip__cell--current .ds-strip__dow{color:var(--cyan)}.ds-strip__cell--current .ds-strip__day{background:var(--cyan);border-color:var(--cyan);color:#fff}.ds-strip__cell--current.ds-strip__cell--weekend .ds-strip__dow{color:var(--indigo)}.ds-strip__cell--current.ds-strip__cell--weekend .ds-strip__day{background:var(--indigo);border-color:var(--indigo)}.ds-strip__dot{width:4px;height:4px;border-radius:2px;background:transparent;display:inline-block}.ds-strip__dot--activity{background:var(--purple)}.ds-strip__dot--event{background:var(--blue)}.ds-strip__legend{display:flex;gap:12px;margin-top:6px;padding-left:2px;font-size:10px;color:var(--muted)}.ds-strip__legend span{display:inline-flex;align-items:center;gap:5px}.ds-head{display:flex;align-items:center;gap:8px;margin-bottom:8px;--tone: var(--cyan)}.ds-head--cyan{--tone: var(--cyan)}.ds-head--orange{--tone: var(--orange)}.ds-head--purple{--tone: var(--purple)}.ds-head--blue{--tone: var(--blue)}.ds-head--green{--tone: var(--green)}.ds-head--pink{--tone: var(--pink)}.ds-head__bar{width:4px;height:16px;border-radius:1px;background:var(--tone)}.ds-head__label{font-size:14px;font-weight:700}.ds-head__count{font-size:11px;font-weight:700;color:#fff;background:var(--tone);border-radius:var(--r-small);padding:2px 7px}.ds-head__spacer{flex:1}.ds-head__action{font-size:11px;font-weight:700;color:var(--tone)}.ds-list{display:flex;flex-direction:column;gap:8px}.ds-empty{background:var(--surface);border:1px dashed var(--border);border-radius:var(--r-card);padding:12px 14px;font-size:12px;color:var(--faint)}.ds-hint{font-size:11px;color:var(--faint)}.ds-hint--amber{color:var(--amber);font-weight:700;margin-top:8px}.ds-tag{display:inline-flex;align-items:center;gap:5px;font-size:11px;font-weight:700;border:1px solid var(--border);border-radius:var(--r-small);padding:2px 8px;white-space:nowrap}.ds-tag--indigo{color:var(--indigo);background:var(--indigo-soft);border-color:var(--indigo-line)}.ds-tag--amber{color:var(--amber);background:var(--amber-soft);border-color:var(--amber-line)}.ds-tag--cyan{color:var(--cyan);background:var(--cyan-soft);border-color:var(--cyan-line)}.ds-gap{background:var(--surface);border:1px solid var(--orange-line);border-left:4px solid var(--orange);border-radius:var(--r-card);padding:11px 14px}.ds-gap__row{display:flex;align-items:center;gap:10px}.ds-gap__time{font-size:13px;font-weight:700;font-variant-numeric:tabular-nums}.ds-gap__track{position:relative;flex:1;height:20px;display:block}.ds-gap__track:before{content:\"\";position:absolute;top:9px;left:0;right:0;height:3px;background:var(--orange-soft)}.ds-gap__fill{position:absolute;top:9px;left:0;height:3px;background:var(--orange)}.ds-gap__value{position:absolute;top:-1px;left:50%;transform:translate(-50%);font-size:11px;font-weight:800;color:#fff;background:var(--orange);border-radius:var(--r-small);padding:2px 8px;white-space:nowrap}.ds-gap__foot{display:flex;justify-content:space-between;align-items:center;gap:10px;margin-top:8px}.ds-gap__note{font-size:12px;color:var(--muted)}.ds-gap__volume{font-size:12px;font-weight:700;white-space:nowrap}.ds-feed-periods{display:flex;flex-direction:column;gap:14px}.ds-period__head{display:flex;align-items:baseline;justify-content:space-between;gap:10px;margin-bottom:6px;padding:0 2px}.ds-period__label{font-size:12px;font-weight:800;letter-spacing:.04em;text-transform:uppercase;color:var(--cyan)}.ds-period__meta{font-size:11px;font-weight:600;color:var(--faint)}.ds-feeds{background:var(--surface);border:1px solid var(--border);border-radius:var(--r-card);padding:12px;display:flex;flex-direction:column;gap:10px}.ds-feed-block{display:flex;flex-direction:column;gap:5px}.ds-feed{display:grid;grid-template-columns:42px minmax(0,1fr) 52px 22px;align-items:center;column-gap:10px}.ds-feed--child{grid-template-columns:42px minmax(0,1fr) 52px 22px;opacity:.92}.ds-feed__children{display:flex;flex-direction:column;gap:5px;padding-left:10px;border-left:2px solid var(--cyan-line);margin-left:6px}.ds-feed__time{width:42px;font-size:12px;font-weight:700;font-variant-numeric:tabular-nums}.ds-feed__time--night{color:var(--indigo)}.ds-feed__time--low{color:var(--amber)}.ds-feed__time--high{color:var(--green)}.ds-feed--child .ds-feed__time{font-weight:600;color:var(--muted)}.ds-feed--child .ds-feed__time.ds-feed__time--night{color:var(--indigo)}.ds-feed__track{height:12px;background:var(--bg);border-radius:3px;overflow:hidden;display:block;min-width:0}.ds-feed--child .ds-feed__track{height:8px}.ds-feed__fill{display:block;height:100%;background:var(--cyan)}.ds-feed__fill--night{background:var(--indigo)}.ds-feed__fill--under{background:var(--amber-strong)}.ds-feed__fill--low{background:var(--amber-strong);opacity:.75}.ds-feed__fill--high{background:var(--green)}.ds-feed__volume{width:52px;text-align:right;font-size:12px;font-weight:700;font-variant-numeric:tabular-nums;white-space:nowrap}.ds-feed--child .ds-feed__volume{font-weight:600;color:var(--muted)}.ds-feed__flag{width:22px;height:18px;display:inline-flex;align-items:center;justify-content:center}.ds-feed__flag .pi{font-size:12px;font-weight:700}.ds-feed__flag--low{color:var(--amber)}.ds-feed__flag--high{color:var(--green)}.ds-card{background:var(--surface);border:1px solid var(--border);border-left:4px solid var(--purple);border-radius:var(--r-card);padding:11px 13px}.ds-card__top{display:flex;align-items:center;gap:8px;margin-bottom:6px}.ds-card__place{font-size:11px;color:var(--faint)}.ds-card__title{font-size:14px;font-weight:700}.ds-card__detail{font-size:12px;color:var(--muted);margin-top:3px;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;word-break:break-word}.ds-card__detail ::ng-deep p{margin:0;display:inline}.ds-card__detail ::ng-deep p:after{content:\" \"}.ds-card__detail ::ng-deep ul,.ds-card__detail ::ng-deep ol{margin:0;padding-left:1.1em}.ds-card__detail ::ng-deep br{display:none}.ds-event{display:flex;gap:12px;align-items:center;background:var(--surface);border:1px solid var(--border);border-left:4px solid var(--blue);border-radius:var(--r-card);padding:11px 13px}.ds-event__time{background:var(--blue);color:#fff;border-radius:var(--r-small);padding:6px 9px;font-size:13px;font-weight:800;white-space:nowrap}.ds-event__title{display:block;font-size:14px;font-weight:700}.ds-event__meta{display:block;font-size:12px;color:var(--muted);margin-top:2px}.ds-related{display:flex;align-items:center;gap:12px;width:100%;text-align:left;background:var(--surface);border:1px solid var(--border);border-left:4px solid var(--pink);border-radius:var(--r-card);padding:10px 12px;cursor:pointer;color:var(--text);font-family:inherit}.ds-related--static{cursor:default}.ds-related__badge{min-width:62px;text-align:center;border:1px solid currentcolor;border-radius:var(--r-small);padding:5px 4px}.ds-related__days{display:block;font-size:15px;font-weight:800}.ds-related__unit{display:block;font-size:10px;font-weight:700}.ds-related__body{flex:1;min-width:0}.ds-related__title{display:block;font-size:13px;font-weight:700}.ds-related__meta{display:block;font-size:11px;color:var(--muted);margin-top:2px}.ds-related__chev{font-size:12px;color:var(--faint)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "pipe", type: SafeRichHtmlPipe, name: "safeRichHtml" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
21118
21312
|
}
|
|
21119
21313
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: DailySummaryComponent, decorators: [{
|
|
21120
21314
|
type: Component,
|
|
21121
|
-
args: [{ selector: 'app-daily-summary', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let s = summary();\n\n<div class=\"ds\">\n <!-- \u0110i\u1EC1u h\u01B0\u1EDBng ng\u00E0y -->\n <div class=\"ds-datebar\" [class.ds-datebar--weekend]=\"s.weekend\">\n <button type=\"button\" class=\"ds-datebar__nav\" (click)=\"shiftDay(-1)\" aria-label=\"Ng\u00E0y tr\u01B0\u1EDBc\">\n <i class=\"pi pi-chevron-left\" aria-hidden=\"true\"></i>\n </button>\n\n <label class=\"ds-datebar__center\">\n <span class=\"ds-datebar__label\">\n {{ s.weekday }}, {{ s.displayDate }}\n </span>\n @if (s.weekend) {\n <span class=\"ds-datebar__badge\">Cu\u1ED1i tu\u1EA7n</span>\n }\n <input\n type=\"date\"\n class=\"ds-datebar__input\"\n [value]=\"s.date\"\n [max]=\"maxDate\"\n (change)=\"onDateInput($any($event.target).value)\"\n aria-label=\"Ch\u1ECDn ng\u00E0y\"\n />\n </label>\n\n <button\n type=\"button\"\n class=\"ds-datebar__nav\"\n [disabled]=\"!canGoForward()\"\n (click)=\"shiftDay(1)\"\n aria-label=\"Ng\u00E0y sau\"\n >\n <i class=\"pi pi-chevron-right\" aria-hidden=\"true\"></i>\n </button>\n </div>\n\n @if (canGoForward()) {\n <button type=\"button\" class=\"ds-reset\" (click)=\"goLatest()\">\n <i class=\"pi pi-replay\" aria-hidden=\"true\"></i>\n V\u1EC1 ng\u00E0y m\u1EDBi nh\u1EA5t ({{ maxDateLabel }})\n </button>\n }\n\n <!-- T\u1ED5ng quan -->\n <section class=\"ds-hero\" aria-label=\"T\u1ED5ng quan trong ng\u00E0y\">\n <div class=\"ds-hero__top\">\n <div>\n <div class=\"ds-hero__label\">T\u1ED4NG L\u01AF\u1EE2NG S\u1EEEA TRONG NG\u00C0Y</div>\n <div class=\"ds-hero__total\">{{ s.totalMl }}<span class=\"ds-hero__unit\">ml</span></div>\n @if (s.hasFeeding || s.yesterday.totalMl > 0) {\n <div\n class=\"ds-hero__delta\"\n [class.ds-hero__delta--up]=\"s.yesterday.deltaMl > 0\"\n [class.ds-hero__delta--down]=\"s.yesterday.deltaMl < 0\"\n [class.ds-hero__delta--flat]=\"s.yesterday.deltaMl === 0\"\n >\n @if (s.yesterday.totalMl === 0 && s.hasFeeding) {\n H\u00F4m qua ch\u01B0a ghi nh\u1EADn c\u1EEF\n } @else if (s.yesterday.deltaMl === 0) {\n B\u1EB1ng h\u00F4m qua ({{ s.yesterday.totalMl }}ml)\n } @else {\n <i\n class=\"pi\"\n [class.pi-arrow-up]=\"s.yesterday.deltaMl > 0\"\n [class.pi-arrow-down]=\"s.yesterday.deltaMl < 0\"\n aria-hidden=\"true\"\n ></i>\n {{ s.yesterday.deltaMl > 0 ? '+' : '' }}{{ s.yesterday.deltaMl }}ml\n @if (s.yesterday.deltaPercent !== null) {\n <span class=\"ds-hero__delta-pct\">\n ({{ s.yesterday.deltaPercent > 0 ? '+' : '' }}{{ s.yesterday.deltaPercent }}%)\n </span>\n }\n so v\u1EDBi h\u00F4m qua\n }\n </div>\n }\n </div>\n <div class=\"ds-hero__right\">\n <div class=\"ds-hero__count\">{{ s.feedCount }} c\u1EEF b\u00FA</div>\n <div class=\"ds-hero__avg\">\n @if (s.feedCount > 0) {\n trung b\u00ECnh {{ s.avgMl }}ml m\u1ED7i c\u1EEF\n } @else {\n ch\u01B0a ghi nh\u1EADn c\u1EEF n\u00E0o\n }\n </div>\n </div>\n </div>\n\n <div class=\"ds-hero__stats\">\n <div class=\"ds-hero__stat\">\n <span class=\"ds-hero__stat-label\">C\u1EEF cao nh\u1EA5t</span>\n <span class=\"ds-hero__stat-value ds-hero__stat-value--green\">\n {{ s.hasFeeding ? s.maxMl + 'ml' : '\u2014' }}\n </span>\n </div>\n <div class=\"ds-hero__stat\">\n <span class=\"ds-hero__stat-label\">C\u1EEF th\u1EA5p nh\u1EA5t</span>\n <span class=\"ds-hero__stat-value ds-hero__stat-value--blue\">\n {{ s.hasFeeding ? s.minMl + 'ml' : '\u2014' }}\n </span>\n </div>\n <div class=\"ds-hero__stat\">\n <span class=\"ds-hero__stat-label\">B\u00FA \u0111\u00EAm</span>\n <span\n class=\"ds-hero__stat-value\"\n [class.ds-hero__stat-value--indigo]=\"s.nightCount > 0\"\n [class.ds-hero__stat-value--green]=\"s.nightCount === 0\"\n >\n {{ s.nightCount > 0 ? s.nightCount + ' c\u1EEF' : 'Kh\u00F4ng c\u00F3' }}\n </span>\n </div>\n </div>\n </section>\n\n @if (s.nightCount > 0) {\n <div class=\"ds-night\">\n B\u00E9 c\u00F3 b\u00FA \u0111\u00EAm trong khung 00:00 \u2013 05:00: {{ s.nightCount }} c\u1EEF b\u00FA, t\u1ED5ng {{ s.nightMl }}ml\n </div>\n }\n\n <!-- D\u1EA3i hai tu\u1EA7n -->\n <section class=\"ds-strip\" aria-label=\"D\u1EA3i hai tu\u1EA7n\">\n <div class=\"ds-strip__frame\">\n <button\n type=\"button\"\n class=\"ds-strip__nav\"\n (click)=\"shiftStripWeek(-2)\"\n aria-label=\"Tu\u1EA7n tr\u01B0\u1EDBc\"\n >\n <i class=\"pi pi-chevron-left\" aria-hidden=\"true\"></i>\n </button>\n\n <div class=\"ds-strip__content\">\n <div class=\"ds-strip__chart\" aria-hidden=\"true\">\n @for (cell of s.strip; track cell.date) {\n <div\n class=\"ds-strip__bar-col\"\n [class.ds-strip__bar-col--current]=\"cell.current\"\n [class.ds-strip__bar-col--weekend]=\"cell.weekend\"\n >\n <div class=\"ds-strip__bar-track\">\n <span\n class=\"ds-strip__bar\"\n [class.ds-strip__bar--empty]=\"!cell.hasFeeding\"\n [style.height.%]=\"cell.barPercent\"\n ></span>\n </div>\n @if (cell.hasFeeding) {\n <span class=\"ds-strip__bar-label\">{{ cell.totalMl }}</span>\n }\n </div>\n }\n </div>\n\n <div class=\"ds-strip__row\">\n @for (cell of s.strip; track cell.date) {\n <button\n type=\"button\"\n class=\"ds-strip__cell\"\n [class.ds-strip__cell--current]=\"cell.current\"\n [class.ds-strip__cell--weekend]=\"cell.weekend\"\n [disabled]=\"!isNavigable(cell.date)\"\n (click)=\"openDate(cell.date)\"\n [attr.aria-current]=\"cell.current ? 'date' : null\"\n [attr.aria-label]=\"cell.weekdayShort + ' ng\u00E0y ' + cell.dayNumber\"\n >\n <span class=\"ds-strip__dow\">{{ cell.weekdayShort }}</span>\n <span class=\"ds-strip__day\" [class.ds-strip__day--data]=\"cell.hasFeeding\">\n {{ cell.dayNumber }}\n </span>\n <span\n class=\"ds-strip__dot\"\n [class.ds-strip__dot--activity]=\"cell.hasActivity\"\n [class.ds-strip__dot--event]=\"!cell.hasActivity && cell.hasEvent\"\n ></span>\n </button>\n }\n </div>\n </div>\n\n <button\n type=\"button\"\n class=\"ds-strip__nav\"\n [disabled]=\"!canShiftStripForward()\"\n (click)=\"shiftStripWeek(2)\"\n aria-label=\"Tu\u1EA7n sau\"\n >\n <i class=\"pi pi-chevron-right\" aria-hidden=\"true\"></i>\n </button>\n </div>\n\n <div class=\"ds-strip__legend\">\n <span><i class=\"ds-strip__dot ds-strip__dot--activity\"></i> C\u00F3 ho\u1EA1t \u0111\u1ED9ng</span>\n <span><i class=\"ds-strip__dot ds-strip__dot--event\"></i> C\u00F3 l\u1ECBch h\u1EB9n</span>\n </div>\n </section>\n\n <!-- Th\u1EC3 tr\u1EA1ng & m\u1EE9c \u0111\u1EA1t khuy\u1EBFn ngh\u1ECB -->\n @if (s.body) {\n <section class=\"ds-body-card\" aria-label=\"Th\u1EC3 tr\u1EA1ng v\u00E0 khuy\u1EBFn ngh\u1ECB\">\n <div class=\"ds-body-card__title\">Th\u1EC3 tr\u1EA1ng & khuy\u1EBFn ngh\u1ECB</div>\n\n <div class=\"ds-growth\">\n <article\n class=\"ds-growth__card\"\n [attr.data-status]=\"s.body.weightWho?.status ?? 'unknown'\"\n >\n <div class=\"ds-growth__head\">\n <span class=\"ds-growth__label\">\n <i class=\"pi pi-heart-fill\" aria-hidden=\"true\"></i>\n C\u00E2n n\u1EB7ng\n </span>\n @if (s.body.weightWho; as ww) {\n <span class=\"ds-growth__badge\" [attr.data-status]=\"ww.status\">\n {{ ww.badgeLabel }}\n </span>\n }\n </div>\n <div class=\"ds-growth__value\">{{ s.body.weightKg }}<span>kg</span></div>\n\n @if (s.body.weightWho; as ww) {\n <div class=\"ds-growth__compare\">\n <span>Hi\u1EC7n t\u1EA1i {{ ww.actual }}kg</span>\n <span>Trung v\u1ECB WHO {{ ww.median }}kg</span>\n </div>\n <div class=\"ds-growth__scale\" aria-hidden=\"true\">\n <div class=\"ds-growth__scale-labels\">\n <span>Th\u1EA5p</span>\n <span class=\"ds-growth__scale-mid\">Ph\u00F9 h\u1EE3p</span>\n <span>Cao</span>\n </div>\n <div class=\"ds-growth__scale-track\">\n <span class=\"ds-growth__band ds-growth__band--low\"></span>\n <span class=\"ds-growth__band ds-growth__band--mid\"></span>\n <span class=\"ds-growth__band ds-growth__band--high\"></span>\n <span\n class=\"ds-growth__marker\"\n [attr.data-status]=\"ww.status\"\n [style.left.%]=\"whoMarkerPosition(ww.zScore)\"\n ></span>\n </div>\n <div class=\"ds-growth__scale-legend\">\n <span>\u22122 SD</span>\n <span>P{{ ww.percentile }} \u00B7 z {{ formatWhoZ(ww.zScore) }}</span>\n <span>+2 SD</span>\n </div>\n </div>\n <div class=\"ds-growth__delta\" [attr.data-status]=\"ww.status\">\n {{ ww.deltaLabel }}\n </div>\n } @else {\n <div class=\"ds-growth__empty\">\n @if (s.body.ageInDays !== null) {\n Th\u00EAm gi\u1EDBi t\u00EDnh trong profile \u0111\u1EC3 so chu\u1EA9n WHO\n } @else {\n C\u1EA7n ng\u00E0y sinh \u0111\u1EC3 so chu\u1EA9n WHO\n }\n </div>\n }\n </article>\n\n <article\n class=\"ds-growth__card\"\n [attr.data-status]=\"s.body.heightWho?.status ?? 'unknown'\"\n >\n <div class=\"ds-growth__head\">\n <span class=\"ds-growth__label\">\n <i class=\"pi pi-arrows-v\" aria-hidden=\"true\"></i>\n Chi\u1EC1u cao\n </span>\n @if (s.body.heightWho; as hw) {\n <span class=\"ds-growth__badge\" [attr.data-status]=\"hw.status\">\n {{ hw.badgeLabel }}\n </span>\n }\n </div>\n <div class=\"ds-growth__value\">\n @if (s.body.heightCm) {\n {{ s.body.heightCm }}<span>cm</span>\n } @else {\n \u2014<span>cm</span>\n }\n </div>\n\n @if (s.body.heightWho; as hw) {\n <div class=\"ds-growth__compare\">\n <span>Hi\u1EC7n t\u1EA1i {{ hw.actual }}cm</span>\n <span>Trung v\u1ECB WHO {{ hw.median }}cm</span>\n </div>\n <div class=\"ds-growth__scale\" aria-hidden=\"true\">\n <div class=\"ds-growth__scale-labels\">\n <span>Th\u1EA5p</span>\n <span class=\"ds-growth__scale-mid\">Ph\u00F9 h\u1EE3p</span>\n <span>Cao</span>\n </div>\n <div class=\"ds-growth__scale-track\">\n <span class=\"ds-growth__band ds-growth__band--low\"></span>\n <span class=\"ds-growth__band ds-growth__band--mid\"></span>\n <span class=\"ds-growth__band ds-growth__band--high\"></span>\n <span\n class=\"ds-growth__marker\"\n [attr.data-status]=\"hw.status\"\n [style.left.%]=\"whoMarkerPosition(hw.zScore)\"\n ></span>\n </div>\n <div class=\"ds-growth__scale-legend\">\n <span>\u22122 SD</span>\n <span>P{{ hw.percentile }} \u00B7 z {{ formatWhoZ(hw.zScore) }}</span>\n <span>+2 SD</span>\n </div>\n </div>\n <div class=\"ds-growth__delta\" [attr.data-status]=\"hw.status\">\n {{ hw.deltaLabel }}\n </div>\n } @else {\n <div class=\"ds-growth__empty\">\n {{ s.body.heightCm ? 'Thi\u1EBFu ng\u00E0y sinh/gi\u1EDBi t\u00EDnh \u0111\u1EC3 so chu\u1EA9n' : 'Ch\u01B0a \u0111o chi\u1EC1u cao' }}\n </div>\n }\n </article>\n </div>\n\n <div class=\"ds-body-card__meta\">\n @if (s.body.daysFromViewedDay === 0) {\n C\u00E2n \u0111\u00FAng ng\u00E0y n\u00E0y\n } @else if (s.body.measuredAfter) {\n C\u00E2n ng\u00E0y {{ s.body.measuredDisplay }}, sau ng\u00E0y n\u00E0y {{ s.body.daysFromViewedDay }} ng\u00E0y\n } @else {\n C\u00E2n g\u1EA7n nh\u1EA5t tr\u01B0\u1EDBc \u0111\u00F3, ng\u00E0y {{ s.body.measuredDisplay }}\n }\n @if (s.body.heightDate && s.body.heightDate !== s.body.measuredDate) {\n \u00B7 \u0111o chi\u1EC1u cao ng\u00E0y {{ s.body.heightDisplay }}\n }\n </div>\n\n @if (s.nutrition) {\n <div class=\"ds-target\">\n <div class=\"ds-target__head\">\n <span class=\"ds-target__label\">L\u01B0\u1EE3ng s\u1EEFa so khuy\u1EBFn ngh\u1ECB</span>\n <span\n class=\"ds-body-card__percent\"\n [class.ds-body-card__percent--low]=\"s.nutrition.percentOfMin < 85\"\n [class.ds-body-card__percent--ok]=\"s.nutrition.percentOfMin >= 85\"\n >\n {{ s.nutrition.percentOfMin }}%\n </span>\n </div>\n <div class=\"ds-target__track\">\n <span\n class=\"ds-target__fill\"\n [class.ds-target__fill--low]=\"s.nutrition.percentOfMin < 85\"\n [style.width.%]=\"s.nutrition.barPercent\"\n ></span>\n </div>\n <div class=\"ds-target__scale\">\n <span>0 ml</span>\n <span>\n Khuy\u1EBFn ngh\u1ECB {{ s.nutrition.dailyMlMin }}\u2013{{ s.nutrition.dailyMlMax }} ml m\u1ED7i ng\u00E0y\n </span>\n </div>\n <div class=\"ds-target__note\">\n @if (s.nutrition.remainingToMin > 0) {\n B\u00E9 \u0111\u00E3 b\u00FA {{ s.totalMl }}ml, c\u00F2n thi\u1EBFu {{ s.nutrition.remainingToMin }}ml n\u1EEFa m\u1EDBi \u0111\u1EA1t\n m\u1EE9c t\u1ED1i thi\u1EC3u.\n } @else {\n {{ s.nutrition.statusLabel }} \u2014 b\u00E9 \u0111\u00E3 b\u00FA {{ s.totalMl }}ml trong ng\u00E0y.\n }\n </div>\n </div>\n } @else {\n <div class=\"ds-body-card__meta\">\n Ch\u01B0a t\u00EDnh \u0111\u01B0\u1EE3c khuy\u1EBFn ngh\u1ECB l\u01B0\u1EE3ng s\u1EEFa v\u00EC thi\u1EBFu ng\u00E0y sinh c\u1EE7a b\u00E9.\n </div>\n }\n </section>\n }\n\n <!-- Nh\u00ECn l\u1EA1i -->\n @if (s.recent.length > 0) {\n <section aria-label=\"Nh\u00ECn l\u1EA1i v\u00E0i ng\u00E0y tr\u01B0\u1EDBc\">\n <div class=\"ds-head ds-head--pink\">\n <span class=\"ds-head__bar\"></span>\n <span class=\"ds-head__label\">Nh\u00ECn l\u1EA1i v\u00E0i ng\u00E0y tr\u01B0\u1EDBc</span>\n <span class=\"ds-head__count\">{{ s.recent.length }}</span>\n <span class=\"ds-head__spacer\"></span>\n <span class=\"ds-head__action\">Y t\u1EBF {{ lookBackDays }} ng\u00E0y g\u1EA7n \u0111\u00E2y</span>\n </div>\n <div class=\"ds-list\">\n @for (item of s.recent; track item.date + item.title) {\n <button\n type=\"button\"\n class=\"ds-related\"\n [style.border-left-color]=\"item.color\"\n (click)=\"openDate(item.date)\"\n >\n <span class=\"ds-related__badge\" [style.color]=\"item.color\">\n <span class=\"ds-related__days\">{{ item.days }}</span>\n <span class=\"ds-related__unit\">ng\u00E0y tr\u01B0\u1EDBc</span>\n </span>\n <span class=\"ds-related__body\">\n <span class=\"ds-related__title\">{{ item.title }}</span>\n <span class=\"ds-related__meta\"> {{ item.weekday }} ng\u00E0y {{ item.displayDate }} </span>\n </span>\n <i class=\"pi pi-chevron-right ds-related__chev\" aria-hidden=\"true\"></i>\n </button>\n }\n </div>\n </section>\n }\n\n <!-- C\u1EEF b\u00FA b\u1ECB d\u00E3n -->\n @if (s.stretchedGaps.length > 0) {\n <section aria-label=\"C\u1EEF b\u00FA b\u1ECB d\u00E3n\">\n <div class=\"ds-head ds-head--orange\">\n <span class=\"ds-head__bar\"></span>\n <span class=\"ds-head__label\">C\u1EEF b\u00FA b\u1ECB d\u00E3n</span>\n <span class=\"ds-head__count\">{{ s.stretchedGaps.length }} l\u1EA7n</span>\n <span class=\"ds-head__spacer\"></span>\n <span class=\"ds-head__action\">Ng\u01B0\u1EE1ng {{ s.stretchThresholdHuman }}</span>\n </div>\n <div class=\"ds-list\">\n @for (gap of s.stretchedGaps; track gap.from + gap.to) {\n <div class=\"ds-gap\">\n <div class=\"ds-gap__row\">\n <span class=\"ds-gap__time\">{{ gap.from }}</span>\n <span class=\"ds-gap__track\">\n <span class=\"ds-gap__fill\" [style.width.%]=\"gap.barPercent\"></span>\n <span class=\"ds-gap__value\">{{ gap.humanGap }}</span>\n </span>\n <span class=\"ds-gap__time\">{{ gap.to }}</span>\n </div>\n <div class=\"ds-gap__foot\">\n <span class=\"ds-gap__note\">\n {{ gap.note || 'Kho\u1EA3ng c\u00E1ch d\u00E0i h\u01A1n nh\u1ECBp b\u00FA th\u01B0\u1EDDng ng\u00E0y' }}\n </span>\n <span class=\"ds-gap__volume\">{{ gap.volume }}ml</span>\n </div>\n </div>\n }\n <div class=\"ds-hint\">\n Kho\u1EA3ng c\u00E1ch trung b\u00ECnh h\u00F4m nay {{ s.avgGapHuman }}, d\u00E0i nh\u1EA5t {{ s.maxGapHuman }}\n </div>\n </div>\n </section>\n }\n\n <!-- C\u1EEF b\u00FA -->\n <section aria-label=\"C\u1EEF b\u00FA\">\n <div class=\"ds-head ds-head--cyan\">\n <span class=\"ds-head__bar\"></span>\n <span class=\"ds-head__label\">C\u1EEF b\u00FA</span>\n @if (s.feedCount > 0) {\n <span class=\"ds-head__count\">{{ s.feedCount }} c\u1EEF b\u00FA</span>\n }\n </div>\n @if (!s.hasFeeding) {\n <div class=\"ds-empty\">Kh\u00F4ng c\u00F3 c\u1EEF b\u00FA n\u00E0o \u0111\u01B0\u1EE3c ghi nh\u1EADn trong ng\u00E0y n\u00E0y.</div>\n } @else {\n <div class=\"ds-feed-periods\">\n @for (period of s.feedPeriods; track period.id) {\n <div class=\"ds-period\" [attr.aria-label]=\"period.label\">\n <div class=\"ds-period__head\">\n <span class=\"ds-period__label\">{{ period.label }}</span>\n <span class=\"ds-period__meta\">\n {{ period.feedCount }} c\u1EEF \u00B7 {{ period.totalMl }}ml\n </span>\n </div>\n <div class=\"ds-feeds\">\n @for (feed of period.feeds; track feed.time + feed.volume + $index) {\n <div class=\"ds-feed-block\">\n <div class=\"ds-feed\">\n <span\n class=\"ds-feed__time\"\n [class.ds-feed__time--night]=\"feed.night\"\n [class.ds-feed__time--low]=\"feed.level === 'under' || feed.level === 'low'\"\n [class.ds-feed__time--high]=\"feed.level === 'high'\"\n >\n {{ feed.time }}\n </span>\n <span class=\"ds-feed__track\">\n <span\n class=\"ds-feed__fill\"\n [class.ds-feed__fill--night]=\"feed.night && feed.level === 'normal'\"\n [class.ds-feed__fill--under]=\"feed.level === 'under'\"\n [class.ds-feed__fill--low]=\"feed.level === 'low'\"\n [class.ds-feed__fill--high]=\"feed.level === 'high'\"\n [style.width.%]=\"feed.barPercent\"\n ></span>\n </span>\n <span class=\"ds-feed__volume\">{{ feed.volume }}ml</span>\n <span class=\"ds-feed__flag\">\n @if (feed.level === 'under' || feed.level === 'low') {\n <i\n class=\"pi pi-arrow-down ds-feed__flag--low\"\n [attr.title]=\"feed.levelLabel\"\n [attr.aria-label]=\"feed.levelLabel\"\n ></i>\n } @else if (feed.level === 'high') {\n <i\n class=\"pi pi-arrow-up ds-feed__flag--high\"\n [attr.title]=\"feed.levelLabel\"\n [attr.aria-label]=\"feed.levelLabel\"\n ></i>\n }\n </span>\n </div>\n @if (feed.memberCount > 1) {\n <div class=\"ds-feed__children\">\n @for (m of feed.members; track m.time + m.volume) {\n <div class=\"ds-feed ds-feed--child\">\n <span\n class=\"ds-feed__time\"\n [class.ds-feed__time--night]=\"m.night\"\n >\n {{ m.time }}\n </span>\n <span class=\"ds-feed__track\">\n <span\n class=\"ds-feed__fill\"\n [class.ds-feed__fill--night]=\"m.night\"\n [style.width.%]=\"m.barPercent\"\n ></span>\n </span>\n <span class=\"ds-feed__volume\">{{ m.volume }}ml</span>\n <span class=\"ds-feed__flag\" aria-hidden=\"true\"></span>\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n </div>\n }\n </div>\n @if (lowFeedCount() > 0) {\n <div class=\"ds-hint ds-hint--amber\">\n {{ lowFeedCount() }} c\u1EEF \u00EDt h\u01A1n m\u1EE9c g\u1EE3i \u00FD\n @if (s.nutrition) {\n kho\u1EA3ng {{ s.nutrition.expectedPerFeed }}ml m\u1ED7i c\u1EEF v\u1EDBi {{ s.feedCount }} c\u1EEF trong ng\u00E0y\n } @else {\n so v\u1EDBi nh\u1ECBp b\u00FA chung c\u1EE7a ng\u00E0y\n }\n </div>\n }\n }\n </section>\n\n <!-- Ho\u1EA1t \u0111\u1ED9ng -->\n <section aria-label=\"Ho\u1EA1t \u0111\u1ED9ng\">\n <div class=\"ds-head ds-head--purple\">\n <span class=\"ds-head__bar\"></span>\n <span class=\"ds-head__label\">Ho\u1EA1t \u0111\u1ED9ng</span>\n @if (s.activities.length > 0) {\n <span class=\"ds-head__count\">{{ s.activities.length }}</span>\n }\n </div>\n @if (loadingMedical()) {\n <div class=\"ds-empty\">\u0110ang t\u1EA3i ho\u1EA1t \u0111\u1ED9ng y t\u1EBF\u2026</div>\n } @else if (s.activities.length === 0) {\n <div class=\"ds-empty\">Kh\u00F4ng c\u00F3 ho\u1EA1t \u0111\u1ED9ng n\u00E0o trong ng\u00E0y n\u00E0y.</div>\n } @else {\n <div class=\"ds-list\">\n @for (act of s.activities; track act.title) {\n <div class=\"ds-card\" [style.border-left-color]=\"act.kindColor\">\n <div class=\"ds-card__top\">\n <span\n class=\"ds-tag\"\n [style.color]=\"act.kindColor\"\n [style.border-color]=\"act.kindColor\"\n >\n <i class=\"fa-solid\" [ngClass]=\"act.kindIcon\" aria-hidden=\"true\"></i>\n {{ act.kindLabel }}\n </span>\n @if (act.place) {\n <span class=\"ds-card__place\">{{ act.place }}</span>\n }\n </div>\n <div class=\"ds-card__title\">{{ act.title }}</div>\n @if (act.detail) {\n <div class=\"ds-card__detail\">{{ act.detail }}</div>\n }\n </div>\n }\n </div>\n }\n </section>\n\n <!-- L\u1ECBch -->\n <section aria-label=\"L\u1ECBch\">\n <div class=\"ds-head ds-head--blue\">\n <span class=\"ds-head__bar\"></span>\n <span class=\"ds-head__label\">L\u1ECBch</span>\n @if (s.events.length > 0) {\n <span class=\"ds-head__count\">{{ s.events.length }}</span>\n }\n </div>\n @if (s.events.length === 0) {\n <div class=\"ds-empty\">Kh\u00F4ng c\u00F3 l\u1ECBch h\u1EB9n n\u00E0o trong ng\u00E0y n\u00E0y.</div>\n } @else {\n <div class=\"ds-list\">\n @for (ev of s.events; track ev.time + ev.title) {\n <div class=\"ds-event\">\n <span class=\"ds-event__time\">{{ ev.time }}</span>\n <span class=\"ds-event__body\">\n <span class=\"ds-event__title\">{{ ev.title }}</span>\n @if (ev.place) {\n <span class=\"ds-event__meta\">{{ ev.place }}</span>\n }\n @if (ev.note) {\n <span class=\"ds-event__meta\">{{ ev.note }}</span>\n }\n </span>\n </div>\n }\n </div>\n }\n </section>\n\n <!-- S\u1EAFp t\u1EDBi -->\n <section aria-label=\"S\u1EAFp t\u1EDBi\">\n <div class=\"ds-head ds-head--green\">\n <span class=\"ds-head__bar\"></span>\n <span class=\"ds-head__label\">S\u1EAFp t\u1EDBi</span>\n @if (s.upcoming.length > 0) {\n <span class=\"ds-head__count\">{{ s.upcoming.length }}</span>\n }\n <span class=\"ds-head__spacer\"></span>\n <span class=\"ds-head__action\">{{ lookAheadDays }} ng\u00E0y t\u1EDBi</span>\n </div>\n @if (s.upcoming.length === 0) {\n <div class=\"ds-empty\">Ch\u01B0a c\u00F3 l\u1ECBch h\u1EB9n n\u00E0o trong {{ lookAheadDays }} ng\u00E0y t\u1EDBi.</div>\n } @else {\n <div class=\"ds-list\">\n @for (item of s.upcoming; track item.date + item.title) {\n <div class=\"ds-related ds-related--static\" [style.border-left-color]=\"item.color\">\n <span class=\"ds-related__badge\" [style.color]=\"item.color\">\n <span class=\"ds-related__days\">{{ item.days }}</span>\n <span class=\"ds-related__unit\">ng\u00E0y n\u1EEFa</span>\n </span>\n <span class=\"ds-related__body\">\n <span class=\"ds-related__title\">{{ item.title }}</span>\n <span class=\"ds-related__meta\"> {{ item.weekday }} ng\u00E0y {{ item.displayDate }} </span>\n </span>\n </div>\n }\n </div>\n }\n </section>\n</div>\n", styles: ["@charset \"UTF-8\";:host{--bg: #f4f6fb;--surface: #ffffff;--border: #dfe5ef;--text: #0b1220;--muted: #5a6a85;--faint: #8c9ab1;--cyan: #0891b2;--cyan-soft: #dff7fd;--cyan-line: #7fdcee;--indigo: #5b5bf0;--indigo-soft: #e9e9ff;--indigo-line: #b4b4fb;--orange: #ef7314;--orange-soft: #fff0e2;--orange-line: #fbc088;--amber: #b45309;--amber-strong: #f59e0b;--amber-soft: #fef6e0;--amber-line: #f6cf7d;--purple: #8b3ff5;--blue: #2563eb;--green: #16a34a;--pink: #e0338c;--r-card: 10px;--r-small: 6px;display:block;background:var(--bg);color:var(--text)}.ds{display:flex;flex-direction:column;gap:18px;padding:14px 14px 32px}.ds-datebar{display:flex;align-items:center;justify-content:space-between;gap:8px;background:var(--cyan-soft);border:1px solid var(--cyan-line);border-radius:var(--r-card);padding:8px 10px}.ds-datebar--weekend{background:var(--indigo-soft);border-color:var(--indigo-line)}.ds-datebar--weekend .ds-datebar__nav{color:var(--indigo)}.ds-datebar--weekend .ds-datebar__nav:disabled{color:var(--faint)}.ds-datebar--weekend .ds-datebar__label{color:var(--indigo)}.ds-datebar__nav{border:none;background:transparent;color:var(--cyan);font-size:15px;padding:6px 10px;cursor:pointer}.ds-datebar__nav:disabled{color:var(--faint);cursor:default}.ds-datebar__center{position:relative;flex:1;min-width:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;padding:4px 8px;cursor:pointer;border-radius:var(--r-small)}.ds-datebar__label{font-size:15px;font-weight:800;color:var(--cyan);text-align:center;line-height:1.25}.ds-datebar__badge{font-size:10px;font-weight:800;letter-spacing:.02em;text-transform:uppercase;color:var(--indigo);background:var(--surface);border:1px solid var(--indigo-line);border-radius:999px;padding:1px 7px}.ds-datebar__input{position:absolute;inset:0;width:100%;height:100%;opacity:0;border:none;cursor:pointer}.ds-reset{align-self:center;margin-top:-12px;display:inline-flex;align-items:center;gap:6px;border:1px solid var(--cyan-line);background:var(--surface);color:var(--cyan);border-radius:999px;padding:6px 14px;font-family:inherit;font-size:12px;font-weight:700;cursor:pointer}.ds-reset i{font-size:12px}.ds-datebar--weekend~.ds-reset{border-color:var(--indigo-line);color:var(--indigo)}.ds-hero{background:var(--cyan-soft);border:1px solid var(--cyan-line);border-radius:var(--r-card);padding:14px;color:var(--text)}.ds-hero__top{display:flex;align-items:flex-end;justify-content:space-between;gap:12px}.ds-hero__label{font-size:11px;font-weight:800;color:var(--cyan)}.ds-hero__total{font-size:38px;font-weight:800;line-height:1.05;margin-top:2px;color:var(--cyan)}.ds-hero__unit{font-size:15px;font-weight:700;margin-left:4px}.ds-hero__delta{display:inline-flex;align-items:center;flex-wrap:wrap;gap:4px;margin-top:6px;font-size:12px;font-weight:700;color:var(--muted)}.ds-hero__delta i{font-size:11px}.ds-hero__delta--up{color:#047857}.ds-hero__delta--down{color:#b45309}.ds-hero__delta--flat{color:var(--muted)}.ds-hero__delta-pct{font-weight:800}.ds-hero__right{text-align:right}.ds-hero__count{font-size:18px;font-weight:800}.ds-hero__avg{font-size:11px;font-weight:600;color:var(--muted)}.ds-hero__stats{display:flex;gap:8px;margin-top:14px}.ds-hero__stat{flex:1;background:var(--surface);border:1px solid var(--cyan-line);border-radius:var(--r-small);padding:8px 10px}.ds-hero__stat-label{display:block;font-size:10px;font-weight:700;color:var(--faint)}.ds-hero__stat-value{display:block;font-size:14px;font-weight:800;margin-top:2px;color:var(--text)}.ds-hero__stat-value--green{color:var(--green)}.ds-hero__stat-value--blue{color:var(--blue)}.ds-hero__stat-value--indigo{color:var(--indigo)}.ds-body-card{background:var(--surface);border:1px solid var(--border);border-left:4px solid var(--green);border-radius:var(--r-card);padding:12px 14px}.ds-body-card__title{font-size:12px;font-weight:800;letter-spacing:.02em;color:var(--muted);margin-bottom:10px}.ds-growth{display:grid;grid-template-columns:1fr 1fr;gap:10px}@media(max-width:420px){.ds-growth{grid-template-columns:1fr}}.ds-growth__card{background:var(--bg);border:1px solid var(--border);border-radius:var(--r-card);padding:10px 11px 11px;min-width:0}.ds-growth__card[data-status=normal]{border-color:#86efac;background:#f0fdf4}.ds-growth__card[data-status=low],.ds-growth__card[data-status=very_low]{border-color:var(--amber-line);background:var(--amber-soft)}.ds-growth__card[data-status=high],.ds-growth__card[data-status=very_high]{border-color:#93c5fd;background:#eff6ff}.ds-growth__head{display:flex;align-items:center;justify-content:space-between;gap:6px;margin-bottom:4px}.ds-growth__label{display:inline-flex;align-items:center;gap:5px;font-size:11px;font-weight:800;color:var(--muted)}.ds-growth__label i{font-size:11px;color:var(--green)}.ds-growth__badge{font-size:10px;font-weight:800;border-radius:999px;padding:2px 7px;white-space:nowrap;background:var(--surface);border:1px solid var(--border);color:var(--muted)}.ds-growth__badge[data-status=normal]{color:var(--green);border-color:#86efac}.ds-growth__badge[data-status=low],.ds-growth__badge[data-status=very_low]{color:var(--amber);border-color:var(--amber-line)}.ds-growth__badge[data-status=high],.ds-growth__badge[data-status=very_high]{color:var(--blue);border-color:#93c5fd}.ds-growth__value{font-size:24px;font-weight:800;line-height:1.1;font-variant-numeric:tabular-nums}.ds-growth__value span{font-size:12px;font-weight:700;margin-left:3px;color:var(--muted)}.ds-growth__compare{display:flex;flex-direction:column;gap:1px;margin-top:6px;font-size:10px;font-weight:600;color:var(--faint)}.ds-growth__scale{margin-top:8px}.ds-growth__scale-labels{display:flex;justify-content:space-between;font-size:9px;font-weight:700;color:var(--faint);margin-bottom:3px}.ds-growth__scale-mid{color:var(--green)}.ds-growth__scale-track{position:relative;display:flex;height:10px;border-radius:5px;overflow:hidden;background:var(--border)}.ds-growth__band{display:block;height:100%}.ds-growth__band--low{width:25%;background:#fcd34d}.ds-growth__band--mid{width:50%;background:#86efac}.ds-growth__band--high{width:25%;background:#93c5fd}.ds-growth__marker{position:absolute;top:50%;width:12px;height:12px;margin-left:-6px;margin-top:-6px;border-radius:50%;background:var(--text);border:2px solid #ffffff;box-sizing:border-box}.ds-growth__marker[data-status=normal]{background:var(--green)}.ds-growth__marker[data-status=low],.ds-growth__marker[data-status=very_low]{background:var(--amber)}.ds-growth__marker[data-status=high],.ds-growth__marker[data-status=very_high]{background:var(--blue)}.ds-growth__scale-legend{display:flex;justify-content:space-between;margin-top:3px;font-size:9px;font-weight:700;color:var(--faint);font-variant-numeric:tabular-nums}.ds-growth__delta{margin-top:7px;font-size:11px;font-weight:700;color:var(--green)}.ds-growth__delta[data-status=low],.ds-growth__delta[data-status=very_low]{color:var(--amber)}.ds-growth__delta[data-status=high],.ds-growth__delta[data-status=very_high]{color:var(--blue)}.ds-growth__delta[data-status=unknown]{color:var(--muted)}.ds-growth__empty{margin-top:8px;font-size:11px;font-weight:600;color:var(--faint);line-height:1.4}.ds-body-card__percent{font-size:18px;font-weight:800;color:var(--green)}.ds-body-card__percent--low{color:var(--amber)}.ds-body-card__meta{font-size:11px;color:var(--faint);margin-top:8px}.ds-target{margin-top:10px;padding-top:10px;border-top:1px solid var(--border)}.ds-target__head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:6px}.ds-target__label{font-size:11px;font-weight:800;color:var(--muted)}.ds-target__track{height:10px;background:var(--bg);border-radius:3px;overflow:hidden}.ds-target__fill{display:block;height:100%;background:var(--green)}.ds-target__fill--low{background:var(--amber-strong)}.ds-target__scale{display:flex;justify-content:space-between;gap:10px;font-size:10px;color:var(--faint);margin-top:4px}.ds-target__note{font-size:12px;color:var(--muted);margin-top:6px}.ds-night{background:var(--indigo-soft);border:1px solid var(--indigo-line);border-left:4px solid var(--indigo);border-radius:var(--r-card);padding:10px 12px;font-size:12px;font-weight:700;color:var(--indigo)}.ds-strip{background:var(--surface);border:1px solid var(--border);border-radius:var(--r-card);padding:10px 6px 8px}.ds-strip__frame{display:flex;align-items:stretch;gap:2px}.ds-strip__nav{flex-shrink:0;align-self:center;width:26px;height:52px;border:1px solid var(--border);border-radius:8px;background:#f8fafc;color:var(--cyan);font-size:12px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;padding:0}.ds-strip__nav:disabled{color:var(--faint);cursor:default;opacity:.45}.ds-strip__content{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}.ds-strip__chart{display:flex;gap:1px;align-items:flex-end;height:56px;padding:0 0 2px}.ds-strip__bar-col{flex:1;min-width:0;display:flex;flex-direction:column;align-items:center;justify-content:flex-end;gap:2px;height:100%}.ds-strip__bar-track{flex:1;width:100%;max-width:18px;display:flex;align-items:flex-end;justify-content:center}.ds-strip__bar{width:100%;min-height:3px;border-radius:4px 4px 2px 2px;background:linear-gradient(180deg,#22d3ee,var(--cyan));transition:height .2s ease}.ds-strip__bar--empty{min-height:2px;height:2px!important;background:#e2e8f0;opacity:.7}.ds-strip__bar-col--current .ds-strip__bar:not(.ds-strip__bar--empty){background:linear-gradient(180deg,#34d399,#059669)}.ds-strip__bar-col--weekend .ds-strip__bar:not(.ds-strip__bar--empty){background:linear-gradient(180deg,#a5b4fc,var(--indigo))}.ds-strip__bar-col--current.ds-strip__bar-col--weekend .ds-strip__bar:not(.ds-strip__bar--empty){background:linear-gradient(180deg,#818cf8,#4f46e5)}.ds-strip__bar-label{font-size:8px;font-weight:800;color:var(--muted);line-height:1;font-variant-numeric:tabular-nums}.ds-strip__bar-col--current .ds-strip__bar-label{color:var(--cyan)}.ds-strip__row{display:flex;gap:1px}.ds-strip__cell{flex:1;min-width:0;border:none;background:transparent;padding:2px 0 0;cursor:pointer;display:flex;flex-direction:column;align-items:center;gap:3px;font-family:inherit}.ds-strip__cell:disabled{cursor:default;opacity:.4}.ds-strip__dow{font-size:9px;font-weight:700;color:var(--faint)}.ds-strip__cell--weekend .ds-strip__dow{color:var(--indigo)}.ds-strip__day{width:22px;height:22px;border-radius:11px;display:inline-flex;align-items:center;justify-content:center;font-size:11px;font-weight:800;color:var(--faint);border:1px solid transparent}.ds-strip__day--data{color:var(--text);border-color:var(--border)}.ds-strip__cell--current .ds-strip__dow{color:var(--cyan)}.ds-strip__cell--current .ds-strip__day{background:var(--cyan);border-color:var(--cyan);color:#fff}.ds-strip__cell--current.ds-strip__cell--weekend .ds-strip__dow{color:var(--indigo)}.ds-strip__cell--current.ds-strip__cell--weekend .ds-strip__day{background:var(--indigo);border-color:var(--indigo)}.ds-strip__dot{width:4px;height:4px;border-radius:2px;background:transparent;display:inline-block}.ds-strip__dot--activity{background:var(--purple)}.ds-strip__dot--event{background:var(--blue)}.ds-strip__legend{display:flex;gap:12px;margin-top:6px;padding-left:2px;font-size:10px;color:var(--muted)}.ds-strip__legend span{display:inline-flex;align-items:center;gap:5px}.ds-head{display:flex;align-items:center;gap:8px;margin-bottom:8px;--tone: var(--cyan)}.ds-head--cyan{--tone: var(--cyan)}.ds-head--orange{--tone: var(--orange)}.ds-head--purple{--tone: var(--purple)}.ds-head--blue{--tone: var(--blue)}.ds-head--green{--tone: var(--green)}.ds-head--pink{--tone: var(--pink)}.ds-head__bar{width:4px;height:16px;border-radius:1px;background:var(--tone)}.ds-head__label{font-size:14px;font-weight:700}.ds-head__count{font-size:11px;font-weight:700;color:#fff;background:var(--tone);border-radius:var(--r-small);padding:2px 7px}.ds-head__spacer{flex:1}.ds-head__action{font-size:11px;font-weight:700;color:var(--tone)}.ds-list{display:flex;flex-direction:column;gap:8px}.ds-empty{background:var(--surface);border:1px dashed var(--border);border-radius:var(--r-card);padding:12px 14px;font-size:12px;color:var(--faint)}.ds-hint{font-size:11px;color:var(--faint)}.ds-hint--amber{color:var(--amber);font-weight:700;margin-top:8px}.ds-tag{display:inline-flex;align-items:center;gap:5px;font-size:11px;font-weight:700;border:1px solid var(--border);border-radius:var(--r-small);padding:2px 8px;white-space:nowrap}.ds-tag--indigo{color:var(--indigo);background:var(--indigo-soft);border-color:var(--indigo-line)}.ds-tag--amber{color:var(--amber);background:var(--amber-soft);border-color:var(--amber-line)}.ds-tag--cyan{color:var(--cyan);background:var(--cyan-soft);border-color:var(--cyan-line)}.ds-gap{background:var(--surface);border:1px solid var(--orange-line);border-left:4px solid var(--orange);border-radius:var(--r-card);padding:11px 14px}.ds-gap__row{display:flex;align-items:center;gap:10px}.ds-gap__time{font-size:13px;font-weight:700;font-variant-numeric:tabular-nums}.ds-gap__track{position:relative;flex:1;height:20px;display:block}.ds-gap__track:before{content:\"\";position:absolute;top:9px;left:0;right:0;height:3px;background:var(--orange-soft)}.ds-gap__fill{position:absolute;top:9px;left:0;height:3px;background:var(--orange)}.ds-gap__value{position:absolute;top:-1px;left:50%;transform:translate(-50%);font-size:11px;font-weight:800;color:#fff;background:var(--orange);border-radius:var(--r-small);padding:2px 8px;white-space:nowrap}.ds-gap__foot{display:flex;justify-content:space-between;align-items:center;gap:10px;margin-top:8px}.ds-gap__note{font-size:12px;color:var(--muted)}.ds-gap__volume{font-size:12px;font-weight:700;white-space:nowrap}.ds-feed-periods{display:flex;flex-direction:column;gap:14px}.ds-period__head{display:flex;align-items:baseline;justify-content:space-between;gap:10px;margin-bottom:6px;padding:0 2px}.ds-period__label{font-size:12px;font-weight:800;letter-spacing:.04em;text-transform:uppercase;color:var(--cyan)}.ds-period__meta{font-size:11px;font-weight:600;color:var(--faint)}.ds-feeds{background:var(--surface);border:1px solid var(--border);border-radius:var(--r-card);padding:12px;display:flex;flex-direction:column;gap:10px}.ds-feed-block{display:flex;flex-direction:column;gap:5px}.ds-feed{display:grid;grid-template-columns:42px minmax(0,1fr) 52px 22px;align-items:center;column-gap:10px}.ds-feed--child{grid-template-columns:42px minmax(0,1fr) 52px 22px;opacity:.92}.ds-feed__children{display:flex;flex-direction:column;gap:5px;padding-left:10px;border-left:2px solid var(--cyan-line);margin-left:6px}.ds-feed__time{width:42px;font-size:12px;font-weight:700;font-variant-numeric:tabular-nums}.ds-feed__time--night{color:var(--indigo)}.ds-feed__time--low{color:var(--amber)}.ds-feed__time--high{color:var(--green)}.ds-feed--child .ds-feed__time{font-weight:600;color:var(--muted)}.ds-feed--child .ds-feed__time.ds-feed__time--night{color:var(--indigo)}.ds-feed__track{height:12px;background:var(--bg);border-radius:3px;overflow:hidden;display:block;min-width:0}.ds-feed--child .ds-feed__track{height:8px}.ds-feed__fill{display:block;height:100%;background:var(--cyan)}.ds-feed__fill--night{background:var(--indigo)}.ds-feed__fill--under{background:var(--amber-strong)}.ds-feed__fill--low{background:var(--amber-strong);opacity:.75}.ds-feed__fill--high{background:var(--green)}.ds-feed__volume{width:52px;text-align:right;font-size:12px;font-weight:700;font-variant-numeric:tabular-nums;white-space:nowrap}.ds-feed--child .ds-feed__volume{font-weight:600;color:var(--muted)}.ds-feed__flag{width:22px;height:18px;display:inline-flex;align-items:center;justify-content:center}.ds-feed__flag .pi{font-size:12px;font-weight:700}.ds-feed__flag--low{color:var(--amber)}.ds-feed__flag--high{color:var(--green)}.ds-card{background:var(--surface);border:1px solid var(--border);border-left:4px solid var(--purple);border-radius:var(--r-card);padding:11px 13px}.ds-card__top{display:flex;align-items:center;gap:8px;margin-bottom:6px}.ds-card__place{font-size:11px;color:var(--faint)}.ds-card__title{font-size:14px;font-weight:700}.ds-card__detail{font-size:12px;color:var(--muted);margin-top:3px}.ds-event{display:flex;gap:12px;align-items:center;background:var(--surface);border:1px solid var(--border);border-left:4px solid var(--blue);border-radius:var(--r-card);padding:11px 13px}.ds-event__time{background:var(--blue);color:#fff;border-radius:var(--r-small);padding:6px 9px;font-size:13px;font-weight:800;white-space:nowrap}.ds-event__title{display:block;font-size:14px;font-weight:700}.ds-event__meta{display:block;font-size:12px;color:var(--muted);margin-top:2px}.ds-related{display:flex;align-items:center;gap:12px;width:100%;text-align:left;background:var(--surface);border:1px solid var(--border);border-left:4px solid var(--pink);border-radius:var(--r-card);padding:10px 12px;cursor:pointer;color:var(--text);font-family:inherit}.ds-related--static{cursor:default}.ds-related__badge{min-width:62px;text-align:center;border:1px solid currentcolor;border-radius:var(--r-small);padding:5px 4px}.ds-related__days{display:block;font-size:15px;font-weight:800}.ds-related__unit{display:block;font-size:10px;font-weight:700}.ds-related__body{flex:1;min-width:0}.ds-related__title{display:block;font-size:13px;font-weight:700}.ds-related__meta{display:block;font-size:11px;color:var(--muted);margin-top:2px}.ds-related__chev{font-size:12px;color:var(--faint)}\n"] }]
|
|
21315
|
+
args: [{ selector: 'app-daily-summary', standalone: true, imports: [CommonModule, SafeRichHtmlPipe], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let s = summary();\n\n<div class=\"ds\">\n <!-- \u0110i\u1EC1u h\u01B0\u1EDBng ng\u00E0y -->\n <div class=\"ds-datebar\" [class.ds-datebar--weekend]=\"s.weekend\">\n <button type=\"button\" class=\"ds-datebar__nav\" (click)=\"shiftDay(-1)\" aria-label=\"Ng\u00E0y tr\u01B0\u1EDBc\">\n <i class=\"pi pi-chevron-left\" aria-hidden=\"true\"></i>\n </button>\n\n <label class=\"ds-datebar__center\">\n <span class=\"ds-datebar__label\">\n {{ s.weekday }}, {{ s.displayDate }}\n </span>\n @if (s.weekend) {\n <span class=\"ds-datebar__badge\">Cu\u1ED1i tu\u1EA7n</span>\n }\n <input\n type=\"date\"\n class=\"ds-datebar__input\"\n [value]=\"s.date\"\n [max]=\"maxDate\"\n (change)=\"onDateInput($any($event.target).value)\"\n aria-label=\"Ch\u1ECDn ng\u00E0y\"\n />\n </label>\n\n <button\n type=\"button\"\n class=\"ds-datebar__nav\"\n [disabled]=\"!canGoForward()\"\n (click)=\"shiftDay(1)\"\n aria-label=\"Ng\u00E0y sau\"\n >\n <i class=\"pi pi-chevron-right\" aria-hidden=\"true\"></i>\n </button>\n </div>\n\n @if (canGoForward()) {\n <button type=\"button\" class=\"ds-reset\" (click)=\"goLatest()\">\n <i class=\"pi pi-replay\" aria-hidden=\"true\"></i>\n V\u1EC1 ng\u00E0y m\u1EDBi nh\u1EA5t ({{ maxDateLabel }})\n </button>\n }\n\n <!-- T\u1ED5ng quan -->\n <section class=\"ds-hero\" aria-label=\"T\u1ED5ng quan trong ng\u00E0y\">\n <div class=\"ds-hero__top\">\n <div>\n <div class=\"ds-hero__label\">T\u1ED4NG L\u01AF\u1EE2NG S\u1EEEA TRONG NG\u00C0Y</div>\n <div class=\"ds-hero__total\">{{ s.totalMl }}<span class=\"ds-hero__unit\">ml</span></div>\n @if (s.hasFeeding || s.yesterday.totalMl > 0) {\n <div\n class=\"ds-hero__delta\"\n [class.ds-hero__delta--up]=\"s.yesterday.deltaMl > 0\"\n [class.ds-hero__delta--down]=\"s.yesterday.deltaMl < 0\"\n [class.ds-hero__delta--flat]=\"s.yesterday.deltaMl === 0\"\n >\n @if (s.yesterday.totalMl === 0 && s.hasFeeding) {\n H\u00F4m qua ch\u01B0a ghi nh\u1EADn c\u1EEF\n } @else if (s.yesterday.deltaMl === 0) {\n B\u1EB1ng h\u00F4m qua ({{ s.yesterday.totalMl }}ml)\n } @else {\n <i\n class=\"pi\"\n [class.pi-arrow-up]=\"s.yesterday.deltaMl > 0\"\n [class.pi-arrow-down]=\"s.yesterday.deltaMl < 0\"\n aria-hidden=\"true\"\n ></i>\n {{ s.yesterday.deltaMl > 0 ? '+' : '' }}{{ s.yesterday.deltaMl }}ml\n @if (s.yesterday.deltaPercent !== null) {\n <span class=\"ds-hero__delta-pct\">\n ({{ s.yesterday.deltaPercent > 0 ? '+' : '' }}{{ s.yesterday.deltaPercent }}%)\n </span>\n }\n so v\u1EDBi h\u00F4m qua\n }\n </div>\n }\n </div>\n <div class=\"ds-hero__right\">\n <div class=\"ds-hero__count\">{{ s.feedCount }} c\u1EEF b\u00FA</div>\n <div class=\"ds-hero__avg\">\n @if (s.feedCount > 0) {\n trung b\u00ECnh {{ s.avgMl }}ml m\u1ED7i c\u1EEF\n } @else {\n ch\u01B0a ghi nh\u1EADn c\u1EEF n\u00E0o\n }\n </div>\n </div>\n </div>\n\n <div class=\"ds-hero__stats\">\n <div class=\"ds-hero__stat\">\n <span class=\"ds-hero__stat-label\">C\u1EEF cao nh\u1EA5t</span>\n <span class=\"ds-hero__stat-value ds-hero__stat-value--green\">\n {{ s.hasFeeding ? s.maxMl + 'ml' : '\u2014' }}\n </span>\n </div>\n <div class=\"ds-hero__stat\">\n <span class=\"ds-hero__stat-label\">C\u1EEF th\u1EA5p nh\u1EA5t</span>\n <span class=\"ds-hero__stat-value ds-hero__stat-value--blue\">\n {{ s.hasFeeding ? s.minMl + 'ml' : '\u2014' }}\n </span>\n </div>\n <div class=\"ds-hero__stat\">\n <span class=\"ds-hero__stat-label\">B\u00FA \u0111\u00EAm</span>\n <span\n class=\"ds-hero__stat-value\"\n [class.ds-hero__stat-value--indigo]=\"s.nightCount > 0\"\n [class.ds-hero__stat-value--green]=\"s.nightCount === 0\"\n >\n {{ s.nightCount > 0 ? s.nightCount + ' c\u1EEF' : 'Kh\u00F4ng c\u00F3' }}\n </span>\n </div>\n </div>\n </section>\n\n @if (s.nightCount > 0) {\n <div class=\"ds-night\">\n B\u00E9 c\u00F3 b\u00FA \u0111\u00EAm trong khung 00:00 \u2013 05:00: {{ s.nightCount }} c\u1EEF b\u00FA, t\u1ED5ng {{ s.nightMl }}ml\n </div>\n }\n\n <!-- D\u1EA3i hai tu\u1EA7n -->\n <section class=\"ds-strip\" aria-label=\"D\u1EA3i hai tu\u1EA7n\">\n <div class=\"ds-strip__frame\">\n <button\n type=\"button\"\n class=\"ds-strip__nav\"\n (click)=\"shiftStripWeek(-2)\"\n aria-label=\"Tu\u1EA7n tr\u01B0\u1EDBc\"\n >\n <i class=\"pi pi-chevron-left\" aria-hidden=\"true\"></i>\n </button>\n\n <div class=\"ds-strip__content\">\n <div class=\"ds-strip__chart\" aria-hidden=\"true\">\n @for (cell of s.strip; track cell.date) {\n <div\n class=\"ds-strip__bar-col\"\n [class.ds-strip__bar-col--current]=\"cell.current\"\n [class.ds-strip__bar-col--weekend]=\"cell.weekend\"\n >\n <div class=\"ds-strip__bar-track\">\n <span\n class=\"ds-strip__bar\"\n [class.ds-strip__bar--empty]=\"!cell.hasFeeding\"\n [style.height.%]=\"cell.barPercent\"\n ></span>\n </div>\n @if (cell.hasFeeding) {\n <span class=\"ds-strip__bar-label\">{{ cell.totalMl }}</span>\n }\n </div>\n }\n </div>\n\n <div class=\"ds-strip__row\">\n @for (cell of s.strip; track cell.date) {\n <button\n type=\"button\"\n class=\"ds-strip__cell\"\n [class.ds-strip__cell--current]=\"cell.current\"\n [class.ds-strip__cell--weekend]=\"cell.weekend\"\n [disabled]=\"!isNavigable(cell.date)\"\n (click)=\"openDate(cell.date)\"\n [attr.aria-current]=\"cell.current ? 'date' : null\"\n [attr.aria-label]=\"cell.weekdayShort + ' ng\u00E0y ' + cell.dayNumber\"\n >\n <span class=\"ds-strip__dow\">{{ cell.weekdayShort }}</span>\n <span class=\"ds-strip__day\" [class.ds-strip__day--data]=\"cell.hasFeeding\">\n {{ cell.dayNumber }}\n </span>\n <span\n class=\"ds-strip__dot\"\n [class.ds-strip__dot--activity]=\"cell.hasActivity\"\n [class.ds-strip__dot--event]=\"!cell.hasActivity && cell.hasEvent\"\n ></span>\n </button>\n }\n </div>\n </div>\n\n <button\n type=\"button\"\n class=\"ds-strip__nav\"\n [disabled]=\"!canShiftStripForward()\"\n (click)=\"shiftStripWeek(2)\"\n aria-label=\"Tu\u1EA7n sau\"\n >\n <i class=\"pi pi-chevron-right\" aria-hidden=\"true\"></i>\n </button>\n </div>\n\n <div class=\"ds-strip__legend\">\n <span><i class=\"ds-strip__dot ds-strip__dot--activity\"></i> C\u00F3 ho\u1EA1t \u0111\u1ED9ng</span>\n <span><i class=\"ds-strip__dot ds-strip__dot--event\"></i> C\u00F3 l\u1ECBch h\u1EB9n</span>\n </div>\n </section>\n\n <!-- Th\u1EC3 tr\u1EA1ng & m\u1EE9c \u0111\u1EA1t khuy\u1EBFn ngh\u1ECB -->\n @if (s.body) {\n <section class=\"ds-body-card\" aria-label=\"Th\u1EC3 tr\u1EA1ng v\u00E0 khuy\u1EBFn ngh\u1ECB\">\n <div class=\"ds-body-card__title\">Th\u1EC3 tr\u1EA1ng & khuy\u1EBFn ngh\u1ECB</div>\n\n <div class=\"ds-growth\">\n <article\n class=\"ds-growth__card\"\n [attr.data-status]=\"s.body.weightWho?.status ?? 'unknown'\"\n >\n <div class=\"ds-growth__head\">\n <span class=\"ds-growth__label\">\n <i class=\"pi pi-heart-fill\" aria-hidden=\"true\"></i>\n C\u00E2n n\u1EB7ng\n </span>\n @if (s.body.weightWho; as ww) {\n <span class=\"ds-growth__badge\" [attr.data-status]=\"ww.status\">\n {{ ww.badgeLabel }}\n </span>\n }\n </div>\n <div class=\"ds-growth__value\">{{ s.body.weightKg }}<span>kg</span></div>\n\n @if (s.body.weightWho; as ww) {\n <div class=\"ds-growth__compare\">\n <span>Hi\u1EC7n t\u1EA1i {{ ww.actual }}kg</span>\n <span>Trung v\u1ECB WHO {{ ww.median }}kg</span>\n </div>\n <div class=\"ds-growth__scale\" aria-hidden=\"true\">\n <div class=\"ds-growth__scale-labels\">\n <span>Th\u1EA5p</span>\n <span class=\"ds-growth__scale-mid\">Ph\u00F9 h\u1EE3p</span>\n <span>Cao</span>\n </div>\n <div class=\"ds-growth__scale-track\">\n <span class=\"ds-growth__band ds-growth__band--low\"></span>\n <span class=\"ds-growth__band ds-growth__band--mid\"></span>\n <span class=\"ds-growth__band ds-growth__band--high\"></span>\n <span\n class=\"ds-growth__marker\"\n [attr.data-status]=\"ww.status\"\n [style.left.%]=\"whoMarkerPosition(ww.zScore)\"\n ></span>\n </div>\n <div class=\"ds-growth__scale-legend\">\n <span>\u22122 SD</span>\n <span>P{{ ww.percentile }} \u00B7 z {{ formatWhoZ(ww.zScore) }}</span>\n <span>+2 SD</span>\n </div>\n </div>\n <div class=\"ds-growth__delta\" [attr.data-status]=\"ww.status\">\n {{ ww.deltaLabel }}\n </div>\n } @else {\n <div class=\"ds-growth__empty\">\n @if (s.body.ageInDays !== null) {\n Th\u00EAm gi\u1EDBi t\u00EDnh trong profile \u0111\u1EC3 so chu\u1EA9n WHO\n } @else {\n C\u1EA7n ng\u00E0y sinh \u0111\u1EC3 so chu\u1EA9n WHO\n }\n </div>\n }\n </article>\n\n <article\n class=\"ds-growth__card\"\n [attr.data-status]=\"s.body.heightWho?.status ?? 'unknown'\"\n >\n <div class=\"ds-growth__head\">\n <span class=\"ds-growth__label\">\n <i class=\"pi pi-arrows-v\" aria-hidden=\"true\"></i>\n Chi\u1EC1u cao\n </span>\n @if (s.body.heightWho; as hw) {\n <span class=\"ds-growth__badge\" [attr.data-status]=\"hw.status\">\n {{ hw.badgeLabel }}\n </span>\n }\n </div>\n <div class=\"ds-growth__value\">\n @if (s.body.heightCm) {\n {{ s.body.heightCm }}<span>cm</span>\n } @else {\n \u2014<span>cm</span>\n }\n </div>\n\n @if (s.body.heightWho; as hw) {\n <div class=\"ds-growth__compare\">\n <span>Hi\u1EC7n t\u1EA1i {{ hw.actual }}cm</span>\n <span>Trung v\u1ECB WHO {{ hw.median }}cm</span>\n </div>\n <div class=\"ds-growth__scale\" aria-hidden=\"true\">\n <div class=\"ds-growth__scale-labels\">\n <span>Th\u1EA5p</span>\n <span class=\"ds-growth__scale-mid\">Ph\u00F9 h\u1EE3p</span>\n <span>Cao</span>\n </div>\n <div class=\"ds-growth__scale-track\">\n <span class=\"ds-growth__band ds-growth__band--low\"></span>\n <span class=\"ds-growth__band ds-growth__band--mid\"></span>\n <span class=\"ds-growth__band ds-growth__band--high\"></span>\n <span\n class=\"ds-growth__marker\"\n [attr.data-status]=\"hw.status\"\n [style.left.%]=\"whoMarkerPosition(hw.zScore)\"\n ></span>\n </div>\n <div class=\"ds-growth__scale-legend\">\n <span>\u22122 SD</span>\n <span>P{{ hw.percentile }} \u00B7 z {{ formatWhoZ(hw.zScore) }}</span>\n <span>+2 SD</span>\n </div>\n </div>\n <div class=\"ds-growth__delta\" [attr.data-status]=\"hw.status\">\n {{ hw.deltaLabel }}\n </div>\n } @else {\n <div class=\"ds-growth__empty\">\n {{ s.body.heightCm ? 'Thi\u1EBFu ng\u00E0y sinh/gi\u1EDBi t\u00EDnh \u0111\u1EC3 so chu\u1EA9n' : 'Ch\u01B0a \u0111o chi\u1EC1u cao' }}\n </div>\n }\n </article>\n </div>\n\n <div class=\"ds-body-card__meta\">\n @if (s.body.daysFromViewedDay === 0) {\n C\u00E2n \u0111\u00FAng ng\u00E0y n\u00E0y\n } @else if (s.body.measuredAfter) {\n C\u00E2n ng\u00E0y {{ s.body.measuredDisplay }}, sau ng\u00E0y n\u00E0y {{ s.body.daysFromViewedDay }} ng\u00E0y\n } @else {\n C\u00E2n g\u1EA7n nh\u1EA5t tr\u01B0\u1EDBc \u0111\u00F3, ng\u00E0y {{ s.body.measuredDisplay }}\n }\n @if (s.body.heightDate && s.body.heightDate !== s.body.measuredDate) {\n \u00B7 \u0111o chi\u1EC1u cao ng\u00E0y {{ s.body.heightDisplay }}\n }\n </div>\n\n @if (s.nutrition) {\n <div class=\"ds-target\">\n <div class=\"ds-target__head\">\n <span class=\"ds-target__label\">L\u01B0\u1EE3ng s\u1EEFa so khuy\u1EBFn ngh\u1ECB</span>\n <span\n class=\"ds-body-card__percent\"\n [class.ds-body-card__percent--low]=\"s.nutrition.percentOfMin < 85\"\n [class.ds-body-card__percent--ok]=\"s.nutrition.percentOfMin >= 85\"\n >\n {{ s.nutrition.percentOfMin }}%\n </span>\n </div>\n <div class=\"ds-target__track\">\n <span\n class=\"ds-target__fill\"\n [class.ds-target__fill--low]=\"s.nutrition.percentOfMin < 85\"\n [style.width.%]=\"s.nutrition.barPercent\"\n ></span>\n </div>\n <div class=\"ds-target__scale\">\n <span>0 ml</span>\n <span>\n Khuy\u1EBFn ngh\u1ECB {{ s.nutrition.dailyMlMin }}\u2013{{ s.nutrition.dailyMlMax }} ml m\u1ED7i ng\u00E0y\n </span>\n </div>\n <div class=\"ds-target__note\">\n @if (s.nutrition.remainingToMin > 0) {\n B\u00E9 \u0111\u00E3 b\u00FA {{ s.totalMl }}ml, c\u00F2n thi\u1EBFu {{ s.nutrition.remainingToMin }}ml n\u1EEFa m\u1EDBi \u0111\u1EA1t\n m\u1EE9c t\u1ED1i thi\u1EC3u.\n } @else {\n {{ s.nutrition.statusLabel }} \u2014 b\u00E9 \u0111\u00E3 b\u00FA {{ s.totalMl }}ml trong ng\u00E0y.\n }\n </div>\n </div>\n } @else {\n <div class=\"ds-body-card__meta\">\n Ch\u01B0a t\u00EDnh \u0111\u01B0\u1EE3c khuy\u1EBFn ngh\u1ECB l\u01B0\u1EE3ng s\u1EEFa v\u00EC thi\u1EBFu ng\u00E0y sinh c\u1EE7a b\u00E9.\n </div>\n }\n </section>\n }\n\n <!-- Nh\u00ECn l\u1EA1i -->\n @if (s.recent.length > 0) {\n <section aria-label=\"Nh\u00ECn l\u1EA1i v\u00E0i ng\u00E0y tr\u01B0\u1EDBc\">\n <div class=\"ds-head ds-head--pink\">\n <span class=\"ds-head__bar\"></span>\n <span class=\"ds-head__label\">Nh\u00ECn l\u1EA1i v\u00E0i ng\u00E0y tr\u01B0\u1EDBc</span>\n <span class=\"ds-head__count\">{{ s.recent.length }}</span>\n <span class=\"ds-head__spacer\"></span>\n <span class=\"ds-head__action\">Y t\u1EBF {{ lookBackDays }} ng\u00E0y g\u1EA7n \u0111\u00E2y</span>\n </div>\n <div class=\"ds-list\">\n @for (item of s.recent; track item.date + item.title) {\n <button\n type=\"button\"\n class=\"ds-related\"\n [style.border-left-color]=\"item.color\"\n (click)=\"openDate(item.date)\"\n >\n <span class=\"ds-related__badge\" [style.color]=\"item.color\">\n <span class=\"ds-related__days\">{{ item.days }}</span>\n <span class=\"ds-related__unit\">ng\u00E0y tr\u01B0\u1EDBc</span>\n </span>\n <span class=\"ds-related__body\">\n <span class=\"ds-related__title\">{{ item.title }}</span>\n <span class=\"ds-related__meta\"> {{ item.weekday }} ng\u00E0y {{ item.displayDate }} </span>\n </span>\n <i class=\"pi pi-chevron-right ds-related__chev\" aria-hidden=\"true\"></i>\n </button>\n }\n </div>\n </section>\n }\n\n <!-- C\u1EEF b\u00FA b\u1ECB d\u00E3n -->\n @if (s.stretchedGaps.length > 0) {\n <section aria-label=\"C\u1EEF b\u00FA b\u1ECB d\u00E3n\">\n <div class=\"ds-head ds-head--orange\">\n <span class=\"ds-head__bar\"></span>\n <span class=\"ds-head__label\">C\u1EEF b\u00FA b\u1ECB d\u00E3n</span>\n <span class=\"ds-head__count\">{{ s.stretchedGaps.length }} l\u1EA7n</span>\n <span class=\"ds-head__spacer\"></span>\n <span class=\"ds-head__action\">Ng\u01B0\u1EE1ng {{ s.stretchThresholdHuman }}</span>\n </div>\n <div class=\"ds-list\">\n @for (gap of s.stretchedGaps; track gap.from + gap.to) {\n <div class=\"ds-gap\">\n <div class=\"ds-gap__row\">\n <span class=\"ds-gap__time\">{{ gap.from }}</span>\n <span class=\"ds-gap__track\">\n <span class=\"ds-gap__fill\" [style.width.%]=\"gap.barPercent\"></span>\n <span class=\"ds-gap__value\">{{ gap.humanGap }}</span>\n </span>\n <span class=\"ds-gap__time\">{{ gap.to }}</span>\n </div>\n <div class=\"ds-gap__foot\">\n <span class=\"ds-gap__note\">\n {{ gap.note || 'Kho\u1EA3ng c\u00E1ch d\u00E0i h\u01A1n nh\u1ECBp b\u00FA th\u01B0\u1EDDng ng\u00E0y' }}\n </span>\n <span class=\"ds-gap__volume\">{{ gap.volume }}ml</span>\n </div>\n </div>\n }\n <div class=\"ds-hint\">\n Kho\u1EA3ng c\u00E1ch trung b\u00ECnh h\u00F4m nay {{ s.avgGapHuman }}, d\u00E0i nh\u1EA5t {{ s.maxGapHuman }}\n </div>\n </div>\n </section>\n }\n\n <!-- C\u1EEF b\u00FA -->\n <section aria-label=\"C\u1EEF b\u00FA\">\n <div class=\"ds-head ds-head--cyan\">\n <span class=\"ds-head__bar\"></span>\n <span class=\"ds-head__label\">C\u1EEF b\u00FA</span>\n @if (s.feedCount > 0) {\n <span class=\"ds-head__count\">{{ s.feedCount }} c\u1EEF b\u00FA</span>\n }\n </div>\n @if (!s.hasFeeding) {\n <div class=\"ds-empty\">Kh\u00F4ng c\u00F3 c\u1EEF b\u00FA n\u00E0o \u0111\u01B0\u1EE3c ghi nh\u1EADn trong ng\u00E0y n\u00E0y.</div>\n } @else {\n <div class=\"ds-feed-periods\">\n @for (period of s.feedPeriods; track period.id) {\n <div class=\"ds-period\" [attr.aria-label]=\"period.label\">\n <div class=\"ds-period__head\">\n <span class=\"ds-period__label\">{{ period.label }}</span>\n <span class=\"ds-period__meta\">\n {{ period.feedCount }} c\u1EEF \u00B7 {{ period.totalMl }}ml\n </span>\n </div>\n <div class=\"ds-feeds\">\n @for (feed of period.feeds; track feed.time + feed.volume + $index) {\n <div class=\"ds-feed-block\">\n <div class=\"ds-feed\">\n <span\n class=\"ds-feed__time\"\n [class.ds-feed__time--night]=\"feed.night\"\n [class.ds-feed__time--low]=\"feed.level === 'under' || feed.level === 'low'\"\n [class.ds-feed__time--high]=\"feed.level === 'high'\"\n >\n {{ feed.time }}\n </span>\n <span class=\"ds-feed__track\">\n <span\n class=\"ds-feed__fill\"\n [class.ds-feed__fill--night]=\"feed.night && feed.level === 'normal'\"\n [class.ds-feed__fill--under]=\"feed.level === 'under'\"\n [class.ds-feed__fill--low]=\"feed.level === 'low'\"\n [class.ds-feed__fill--high]=\"feed.level === 'high'\"\n [style.width.%]=\"feed.barPercent\"\n ></span>\n </span>\n <span class=\"ds-feed__volume\">{{ feed.volume }}ml</span>\n <span class=\"ds-feed__flag\">\n @if (feed.level === 'under' || feed.level === 'low') {\n <i\n class=\"pi pi-arrow-down ds-feed__flag--low\"\n [attr.title]=\"feed.levelLabel\"\n [attr.aria-label]=\"feed.levelLabel\"\n ></i>\n } @else if (feed.level === 'high') {\n <i\n class=\"pi pi-arrow-up ds-feed__flag--high\"\n [attr.title]=\"feed.levelLabel\"\n [attr.aria-label]=\"feed.levelLabel\"\n ></i>\n }\n </span>\n </div>\n @if (feed.memberCount > 1) {\n <div class=\"ds-feed__children\">\n @for (m of feed.members; track m.time + m.volume) {\n <div class=\"ds-feed ds-feed--child\">\n <span\n class=\"ds-feed__time\"\n [class.ds-feed__time--night]=\"m.night\"\n >\n {{ m.time }}\n </span>\n <span class=\"ds-feed__track\">\n <span\n class=\"ds-feed__fill\"\n [class.ds-feed__fill--night]=\"m.night\"\n [style.width.%]=\"m.barPercent\"\n ></span>\n </span>\n <span class=\"ds-feed__volume\">{{ m.volume }}ml</span>\n <span class=\"ds-feed__flag\" aria-hidden=\"true\"></span>\n </div>\n }\n </div>\n }\n </div>\n }\n </div>\n </div>\n }\n </div>\n @if (lowFeedCount() > 0) {\n <div class=\"ds-hint ds-hint--amber\">\n {{ lowFeedCount() }} c\u1EEF \u00EDt h\u01A1n m\u1EE9c g\u1EE3i \u00FD\n @if (s.nutrition) {\n kho\u1EA3ng {{ s.nutrition.expectedPerFeed }}ml m\u1ED7i c\u1EEF v\u1EDBi {{ s.feedCount }} c\u1EEF trong ng\u00E0y\n } @else {\n so v\u1EDBi nh\u1ECBp b\u00FA chung c\u1EE7a ng\u00E0y\n }\n </div>\n }\n }\n </section>\n\n <!-- Ho\u1EA1t \u0111\u1ED9ng -->\n <section aria-label=\"Ho\u1EA1t \u0111\u1ED9ng\">\n <div class=\"ds-head ds-head--purple\">\n <span class=\"ds-head__bar\"></span>\n <span class=\"ds-head__label\">Ho\u1EA1t \u0111\u1ED9ng</span>\n @if (s.activities.length > 0) {\n <span class=\"ds-head__count\">{{ s.activities.length }}</span>\n }\n </div>\n @if (loadingMedical()) {\n <div class=\"ds-empty\">\u0110ang t\u1EA3i ho\u1EA1t \u0111\u1ED9ng y t\u1EBF\u2026</div>\n } @else if (s.activities.length === 0) {\n <div class=\"ds-empty\">Kh\u00F4ng c\u00F3 ho\u1EA1t \u0111\u1ED9ng n\u00E0o trong ng\u00E0y n\u00E0y.</div>\n } @else {\n <div class=\"ds-list\">\n @for (act of s.activities; track act.title) {\n <div class=\"ds-card\" [style.border-left-color]=\"act.kindColor\">\n <div class=\"ds-card__top\">\n <span\n class=\"ds-tag\"\n [style.color]=\"act.kindColor\"\n [style.border-color]=\"act.kindColor\"\n >\n <i class=\"fa-solid\" [ngClass]=\"act.kindIcon\" aria-hidden=\"true\"></i>\n {{ act.kindLabel }}\n </span>\n @if (act.place) {\n <span class=\"ds-card__place\">{{ act.place }}</span>\n }\n </div>\n <div class=\"ds-card__title\">{{ act.title }}</div>\n @if (act.detail) {\n <div class=\"ds-card__detail\" [innerHTML]=\"act.detail | safeRichHtml\"></div>\n }\n </div>\n }\n </div>\n }\n </section>\n\n <!-- L\u1ECBch -->\n <section aria-label=\"L\u1ECBch\">\n <div class=\"ds-head ds-head--blue\">\n <span class=\"ds-head__bar\"></span>\n <span class=\"ds-head__label\">L\u1ECBch</span>\n @if (s.events.length > 0) {\n <span class=\"ds-head__count\">{{ s.events.length }}</span>\n }\n </div>\n @if (s.events.length === 0) {\n <div class=\"ds-empty\">Kh\u00F4ng c\u00F3 l\u1ECBch h\u1EB9n n\u00E0o trong ng\u00E0y n\u00E0y.</div>\n } @else {\n <div class=\"ds-list\">\n @for (ev of s.events; track ev.time + ev.title) {\n <div class=\"ds-event\">\n <span class=\"ds-event__time\">{{ ev.time }}</span>\n <span class=\"ds-event__body\">\n <span class=\"ds-event__title\">{{ ev.title }}</span>\n @if (ev.place) {\n <span class=\"ds-event__meta\">{{ ev.place }}</span>\n }\n @if (ev.note) {\n <span class=\"ds-event__meta\">{{ ev.note }}</span>\n }\n </span>\n </div>\n }\n </div>\n }\n </section>\n\n <!-- S\u1EAFp t\u1EDBi -->\n <section aria-label=\"S\u1EAFp t\u1EDBi\">\n <div class=\"ds-head ds-head--green\">\n <span class=\"ds-head__bar\"></span>\n <span class=\"ds-head__label\">S\u1EAFp t\u1EDBi</span>\n @if (s.upcoming.length > 0) {\n <span class=\"ds-head__count\">{{ s.upcoming.length }}</span>\n }\n <span class=\"ds-head__spacer\"></span>\n <span class=\"ds-head__action\">{{ lookAheadDays }} ng\u00E0y t\u1EDBi</span>\n </div>\n @if (s.upcoming.length === 0) {\n <div class=\"ds-empty\">Ch\u01B0a c\u00F3 l\u1ECBch h\u1EB9n n\u00E0o trong {{ lookAheadDays }} ng\u00E0y t\u1EDBi.</div>\n } @else {\n <div class=\"ds-list\">\n @for (item of s.upcoming; track item.date + item.title) {\n <div class=\"ds-related ds-related--static\" [style.border-left-color]=\"item.color\">\n <span class=\"ds-related__badge\" [style.color]=\"item.color\">\n <span class=\"ds-related__days\">{{ item.days }}</span>\n <span class=\"ds-related__unit\">ng\u00E0y n\u1EEFa</span>\n </span>\n <span class=\"ds-related__body\">\n <span class=\"ds-related__title\">{{ item.title }}</span>\n <span class=\"ds-related__meta\"> {{ item.weekday }} ng\u00E0y {{ item.displayDate }} </span>\n </span>\n </div>\n }\n </div>\n }\n </section>\n</div>\n", styles: ["@charset \"UTF-8\";:host{--bg: #f4f6fb;--surface: #ffffff;--border: #dfe5ef;--text: #0b1220;--muted: #5a6a85;--faint: #8c9ab1;--cyan: #0891b2;--cyan-soft: #dff7fd;--cyan-line: #7fdcee;--indigo: #5b5bf0;--indigo-soft: #e9e9ff;--indigo-line: #b4b4fb;--orange: #ef7314;--orange-soft: #fff0e2;--orange-line: #fbc088;--amber: #b45309;--amber-strong: #f59e0b;--amber-soft: #fef6e0;--amber-line: #f6cf7d;--purple: #8b3ff5;--blue: #2563eb;--green: #16a34a;--pink: #e0338c;--r-card: 10px;--r-small: 6px;display:block;background:var(--bg);color:var(--text)}.ds{display:flex;flex-direction:column;gap:18px;padding:14px 14px 32px}.ds-datebar{display:flex;align-items:center;justify-content:space-between;gap:8px;background:var(--cyan-soft);border:1px solid var(--cyan-line);border-radius:var(--r-card);padding:8px 10px}.ds-datebar--weekend{background:var(--indigo-soft);border-color:var(--indigo-line)}.ds-datebar--weekend .ds-datebar__nav{color:var(--indigo)}.ds-datebar--weekend .ds-datebar__nav:disabled{color:var(--faint)}.ds-datebar--weekend .ds-datebar__label{color:var(--indigo)}.ds-datebar__nav{border:none;background:transparent;color:var(--cyan);font-size:15px;padding:6px 10px;cursor:pointer}.ds-datebar__nav:disabled{color:var(--faint);cursor:default}.ds-datebar__center{position:relative;flex:1;min-width:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;padding:4px 8px;cursor:pointer;border-radius:var(--r-small)}.ds-datebar__label{font-size:15px;font-weight:800;color:var(--cyan);text-align:center;line-height:1.25}.ds-datebar__badge{font-size:10px;font-weight:800;letter-spacing:.02em;text-transform:uppercase;color:var(--indigo);background:var(--surface);border:1px solid var(--indigo-line);border-radius:999px;padding:1px 7px}.ds-datebar__input{position:absolute;inset:0;width:100%;height:100%;opacity:0;border:none;cursor:pointer}.ds-reset{align-self:center;margin-top:-12px;display:inline-flex;align-items:center;gap:6px;border:1px solid var(--cyan-line);background:var(--surface);color:var(--cyan);border-radius:999px;padding:6px 14px;font-family:inherit;font-size:12px;font-weight:700;cursor:pointer}.ds-reset i{font-size:12px}.ds-datebar--weekend~.ds-reset{border-color:var(--indigo-line);color:var(--indigo)}.ds-hero{background:var(--cyan-soft);border:1px solid var(--cyan-line);border-radius:var(--r-card);padding:14px;color:var(--text)}.ds-hero__top{display:flex;align-items:flex-end;justify-content:space-between;gap:12px}.ds-hero__label{font-size:11px;font-weight:800;color:var(--cyan)}.ds-hero__total{font-size:38px;font-weight:800;line-height:1.05;margin-top:2px;color:var(--cyan)}.ds-hero__unit{font-size:15px;font-weight:700;margin-left:4px}.ds-hero__delta{display:inline-flex;align-items:center;flex-wrap:wrap;gap:4px;margin-top:6px;font-size:12px;font-weight:700;color:var(--muted)}.ds-hero__delta i{font-size:11px}.ds-hero__delta--up{color:#047857}.ds-hero__delta--down{color:#b45309}.ds-hero__delta--flat{color:var(--muted)}.ds-hero__delta-pct{font-weight:800}.ds-hero__right{text-align:right}.ds-hero__count{font-size:18px;font-weight:800}.ds-hero__avg{font-size:11px;font-weight:600;color:var(--muted)}.ds-hero__stats{display:flex;gap:8px;margin-top:14px}.ds-hero__stat{flex:1;background:var(--surface);border:1px solid var(--cyan-line);border-radius:var(--r-small);padding:8px 10px}.ds-hero__stat-label{display:block;font-size:10px;font-weight:700;color:var(--faint)}.ds-hero__stat-value{display:block;font-size:14px;font-weight:800;margin-top:2px;color:var(--text)}.ds-hero__stat-value--green{color:var(--green)}.ds-hero__stat-value--blue{color:var(--blue)}.ds-hero__stat-value--indigo{color:var(--indigo)}.ds-body-card{background:var(--surface);border:1px solid var(--border);border-left:4px solid var(--green);border-radius:var(--r-card);padding:12px 14px}.ds-body-card__title{font-size:12px;font-weight:800;letter-spacing:.02em;color:var(--muted);margin-bottom:10px}.ds-growth{display:grid;grid-template-columns:1fr 1fr;gap:10px}@media(max-width:420px){.ds-growth{grid-template-columns:1fr}}.ds-growth__card{background:var(--bg);border:1px solid var(--border);border-radius:var(--r-card);padding:10px 11px 11px;min-width:0}.ds-growth__card[data-status=normal]{border-color:#86efac;background:#f0fdf4}.ds-growth__card[data-status=low],.ds-growth__card[data-status=very_low]{border-color:var(--amber-line);background:var(--amber-soft)}.ds-growth__card[data-status=high],.ds-growth__card[data-status=very_high]{border-color:#93c5fd;background:#eff6ff}.ds-growth__head{display:flex;align-items:center;justify-content:space-between;gap:6px;margin-bottom:4px}.ds-growth__label{display:inline-flex;align-items:center;gap:5px;font-size:11px;font-weight:800;color:var(--muted)}.ds-growth__label i{font-size:11px;color:var(--green)}.ds-growth__badge{font-size:10px;font-weight:800;border-radius:999px;padding:2px 7px;white-space:nowrap;background:var(--surface);border:1px solid var(--border);color:var(--muted)}.ds-growth__badge[data-status=normal]{color:var(--green);border-color:#86efac}.ds-growth__badge[data-status=low],.ds-growth__badge[data-status=very_low]{color:var(--amber);border-color:var(--amber-line)}.ds-growth__badge[data-status=high],.ds-growth__badge[data-status=very_high]{color:var(--blue);border-color:#93c5fd}.ds-growth__value{font-size:24px;font-weight:800;line-height:1.1;font-variant-numeric:tabular-nums}.ds-growth__value span{font-size:12px;font-weight:700;margin-left:3px;color:var(--muted)}.ds-growth__compare{display:flex;flex-direction:column;gap:1px;margin-top:6px;font-size:10px;font-weight:600;color:var(--faint)}.ds-growth__scale{margin-top:8px}.ds-growth__scale-labels{display:flex;justify-content:space-between;font-size:9px;font-weight:700;color:var(--faint);margin-bottom:3px}.ds-growth__scale-mid{color:var(--green)}.ds-growth__scale-track{position:relative;display:flex;height:10px;border-radius:5px;overflow:hidden;background:var(--border)}.ds-growth__band{display:block;height:100%}.ds-growth__band--low{width:25%;background:#fcd34d}.ds-growth__band--mid{width:50%;background:#86efac}.ds-growth__band--high{width:25%;background:#93c5fd}.ds-growth__marker{position:absolute;top:50%;width:12px;height:12px;margin-left:-6px;margin-top:-6px;border-radius:50%;background:var(--text);border:2px solid #ffffff;box-sizing:border-box}.ds-growth__marker[data-status=normal]{background:var(--green)}.ds-growth__marker[data-status=low],.ds-growth__marker[data-status=very_low]{background:var(--amber)}.ds-growth__marker[data-status=high],.ds-growth__marker[data-status=very_high]{background:var(--blue)}.ds-growth__scale-legend{display:flex;justify-content:space-between;margin-top:3px;font-size:9px;font-weight:700;color:var(--faint);font-variant-numeric:tabular-nums}.ds-growth__delta{margin-top:7px;font-size:11px;font-weight:700;color:var(--green)}.ds-growth__delta[data-status=low],.ds-growth__delta[data-status=very_low]{color:var(--amber)}.ds-growth__delta[data-status=high],.ds-growth__delta[data-status=very_high]{color:var(--blue)}.ds-growth__delta[data-status=unknown]{color:var(--muted)}.ds-growth__empty{margin-top:8px;font-size:11px;font-weight:600;color:var(--faint);line-height:1.4}.ds-body-card__percent{font-size:18px;font-weight:800;color:var(--green)}.ds-body-card__percent--low{color:var(--amber)}.ds-body-card__meta{font-size:11px;color:var(--faint);margin-top:8px}.ds-target{margin-top:10px;padding-top:10px;border-top:1px solid var(--border)}.ds-target__head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:6px}.ds-target__label{font-size:11px;font-weight:800;color:var(--muted)}.ds-target__track{height:10px;background:var(--bg);border-radius:3px;overflow:hidden}.ds-target__fill{display:block;height:100%;background:var(--green)}.ds-target__fill--low{background:var(--amber-strong)}.ds-target__scale{display:flex;justify-content:space-between;gap:10px;font-size:10px;color:var(--faint);margin-top:4px}.ds-target__note{font-size:12px;color:var(--muted);margin-top:6px}.ds-night{background:var(--indigo-soft);border:1px solid var(--indigo-line);border-left:4px solid var(--indigo);border-radius:var(--r-card);padding:10px 12px;font-size:12px;font-weight:700;color:var(--indigo)}.ds-strip{background:var(--surface);border:1px solid var(--border);border-radius:var(--r-card);padding:10px 6px 8px}.ds-strip__frame{display:flex;align-items:stretch;gap:2px}.ds-strip__nav{flex-shrink:0;align-self:center;width:26px;height:52px;border:1px solid var(--border);border-radius:8px;background:#f8fafc;color:var(--cyan);font-size:12px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;padding:0}.ds-strip__nav:disabled{color:var(--faint);cursor:default;opacity:.45}.ds-strip__content{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}.ds-strip__chart{display:flex;gap:1px;align-items:flex-end;height:56px;padding:0 0 2px}.ds-strip__bar-col{flex:1;min-width:0;display:flex;flex-direction:column;align-items:center;justify-content:flex-end;gap:2px;height:100%}.ds-strip__bar-track{flex:1;width:100%;max-width:18px;display:flex;align-items:flex-end;justify-content:center}.ds-strip__bar{width:100%;min-height:3px;border-radius:4px 4px 2px 2px;background:linear-gradient(180deg,#22d3ee,var(--cyan));transition:height .2s ease}.ds-strip__bar--empty{min-height:2px;height:2px!important;background:#e2e8f0;opacity:.7}.ds-strip__bar-col--current .ds-strip__bar:not(.ds-strip__bar--empty){background:linear-gradient(180deg,#34d399,#059669)}.ds-strip__bar-col--weekend .ds-strip__bar:not(.ds-strip__bar--empty){background:linear-gradient(180deg,#a5b4fc,var(--indigo))}.ds-strip__bar-col--current.ds-strip__bar-col--weekend .ds-strip__bar:not(.ds-strip__bar--empty){background:linear-gradient(180deg,#818cf8,#4f46e5)}.ds-strip__bar-label{font-size:8px;font-weight:800;color:var(--muted);line-height:1;font-variant-numeric:tabular-nums}.ds-strip__bar-col--current .ds-strip__bar-label{color:var(--cyan)}.ds-strip__row{display:flex;gap:1px}.ds-strip__cell{flex:1;min-width:0;border:none;background:transparent;padding:2px 0 0;cursor:pointer;display:flex;flex-direction:column;align-items:center;gap:3px;font-family:inherit}.ds-strip__cell:disabled{cursor:default;opacity:.4}.ds-strip__dow{font-size:9px;font-weight:700;color:var(--faint)}.ds-strip__cell--weekend .ds-strip__dow{color:var(--indigo)}.ds-strip__day{width:22px;height:22px;border-radius:11px;display:inline-flex;align-items:center;justify-content:center;font-size:11px;font-weight:800;color:var(--faint);border:1px solid transparent}.ds-strip__day--data{color:var(--text);border-color:var(--border)}.ds-strip__cell--current .ds-strip__dow{color:var(--cyan)}.ds-strip__cell--current .ds-strip__day{background:var(--cyan);border-color:var(--cyan);color:#fff}.ds-strip__cell--current.ds-strip__cell--weekend .ds-strip__dow{color:var(--indigo)}.ds-strip__cell--current.ds-strip__cell--weekend .ds-strip__day{background:var(--indigo);border-color:var(--indigo)}.ds-strip__dot{width:4px;height:4px;border-radius:2px;background:transparent;display:inline-block}.ds-strip__dot--activity{background:var(--purple)}.ds-strip__dot--event{background:var(--blue)}.ds-strip__legend{display:flex;gap:12px;margin-top:6px;padding-left:2px;font-size:10px;color:var(--muted)}.ds-strip__legend span{display:inline-flex;align-items:center;gap:5px}.ds-head{display:flex;align-items:center;gap:8px;margin-bottom:8px;--tone: var(--cyan)}.ds-head--cyan{--tone: var(--cyan)}.ds-head--orange{--tone: var(--orange)}.ds-head--purple{--tone: var(--purple)}.ds-head--blue{--tone: var(--blue)}.ds-head--green{--tone: var(--green)}.ds-head--pink{--tone: var(--pink)}.ds-head__bar{width:4px;height:16px;border-radius:1px;background:var(--tone)}.ds-head__label{font-size:14px;font-weight:700}.ds-head__count{font-size:11px;font-weight:700;color:#fff;background:var(--tone);border-radius:var(--r-small);padding:2px 7px}.ds-head__spacer{flex:1}.ds-head__action{font-size:11px;font-weight:700;color:var(--tone)}.ds-list{display:flex;flex-direction:column;gap:8px}.ds-empty{background:var(--surface);border:1px dashed var(--border);border-radius:var(--r-card);padding:12px 14px;font-size:12px;color:var(--faint)}.ds-hint{font-size:11px;color:var(--faint)}.ds-hint--amber{color:var(--amber);font-weight:700;margin-top:8px}.ds-tag{display:inline-flex;align-items:center;gap:5px;font-size:11px;font-weight:700;border:1px solid var(--border);border-radius:var(--r-small);padding:2px 8px;white-space:nowrap}.ds-tag--indigo{color:var(--indigo);background:var(--indigo-soft);border-color:var(--indigo-line)}.ds-tag--amber{color:var(--amber);background:var(--amber-soft);border-color:var(--amber-line)}.ds-tag--cyan{color:var(--cyan);background:var(--cyan-soft);border-color:var(--cyan-line)}.ds-gap{background:var(--surface);border:1px solid var(--orange-line);border-left:4px solid var(--orange);border-radius:var(--r-card);padding:11px 14px}.ds-gap__row{display:flex;align-items:center;gap:10px}.ds-gap__time{font-size:13px;font-weight:700;font-variant-numeric:tabular-nums}.ds-gap__track{position:relative;flex:1;height:20px;display:block}.ds-gap__track:before{content:\"\";position:absolute;top:9px;left:0;right:0;height:3px;background:var(--orange-soft)}.ds-gap__fill{position:absolute;top:9px;left:0;height:3px;background:var(--orange)}.ds-gap__value{position:absolute;top:-1px;left:50%;transform:translate(-50%);font-size:11px;font-weight:800;color:#fff;background:var(--orange);border-radius:var(--r-small);padding:2px 8px;white-space:nowrap}.ds-gap__foot{display:flex;justify-content:space-between;align-items:center;gap:10px;margin-top:8px}.ds-gap__note{font-size:12px;color:var(--muted)}.ds-gap__volume{font-size:12px;font-weight:700;white-space:nowrap}.ds-feed-periods{display:flex;flex-direction:column;gap:14px}.ds-period__head{display:flex;align-items:baseline;justify-content:space-between;gap:10px;margin-bottom:6px;padding:0 2px}.ds-period__label{font-size:12px;font-weight:800;letter-spacing:.04em;text-transform:uppercase;color:var(--cyan)}.ds-period__meta{font-size:11px;font-weight:600;color:var(--faint)}.ds-feeds{background:var(--surface);border:1px solid var(--border);border-radius:var(--r-card);padding:12px;display:flex;flex-direction:column;gap:10px}.ds-feed-block{display:flex;flex-direction:column;gap:5px}.ds-feed{display:grid;grid-template-columns:42px minmax(0,1fr) 52px 22px;align-items:center;column-gap:10px}.ds-feed--child{grid-template-columns:42px minmax(0,1fr) 52px 22px;opacity:.92}.ds-feed__children{display:flex;flex-direction:column;gap:5px;padding-left:10px;border-left:2px solid var(--cyan-line);margin-left:6px}.ds-feed__time{width:42px;font-size:12px;font-weight:700;font-variant-numeric:tabular-nums}.ds-feed__time--night{color:var(--indigo)}.ds-feed__time--low{color:var(--amber)}.ds-feed__time--high{color:var(--green)}.ds-feed--child .ds-feed__time{font-weight:600;color:var(--muted)}.ds-feed--child .ds-feed__time.ds-feed__time--night{color:var(--indigo)}.ds-feed__track{height:12px;background:var(--bg);border-radius:3px;overflow:hidden;display:block;min-width:0}.ds-feed--child .ds-feed__track{height:8px}.ds-feed__fill{display:block;height:100%;background:var(--cyan)}.ds-feed__fill--night{background:var(--indigo)}.ds-feed__fill--under{background:var(--amber-strong)}.ds-feed__fill--low{background:var(--amber-strong);opacity:.75}.ds-feed__fill--high{background:var(--green)}.ds-feed__volume{width:52px;text-align:right;font-size:12px;font-weight:700;font-variant-numeric:tabular-nums;white-space:nowrap}.ds-feed--child .ds-feed__volume{font-weight:600;color:var(--muted)}.ds-feed__flag{width:22px;height:18px;display:inline-flex;align-items:center;justify-content:center}.ds-feed__flag .pi{font-size:12px;font-weight:700}.ds-feed__flag--low{color:var(--amber)}.ds-feed__flag--high{color:var(--green)}.ds-card{background:var(--surface);border:1px solid var(--border);border-left:4px solid var(--purple);border-radius:var(--r-card);padding:11px 13px}.ds-card__top{display:flex;align-items:center;gap:8px;margin-bottom:6px}.ds-card__place{font-size:11px;color:var(--faint)}.ds-card__title{font-size:14px;font-weight:700}.ds-card__detail{font-size:12px;color:var(--muted);margin-top:3px;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;word-break:break-word}.ds-card__detail ::ng-deep p{margin:0;display:inline}.ds-card__detail ::ng-deep p:after{content:\" \"}.ds-card__detail ::ng-deep ul,.ds-card__detail ::ng-deep ol{margin:0;padding-left:1.1em}.ds-card__detail ::ng-deep br{display:none}.ds-event{display:flex;gap:12px;align-items:center;background:var(--surface);border:1px solid var(--border);border-left:4px solid var(--blue);border-radius:var(--r-card);padding:11px 13px}.ds-event__time{background:var(--blue);color:#fff;border-radius:var(--r-small);padding:6px 9px;font-size:13px;font-weight:800;white-space:nowrap}.ds-event__title{display:block;font-size:14px;font-weight:700}.ds-event__meta{display:block;font-size:12px;color:var(--muted);margin-top:2px}.ds-related{display:flex;align-items:center;gap:12px;width:100%;text-align:left;background:var(--surface);border:1px solid var(--border);border-left:4px solid var(--pink);border-radius:var(--r-card);padding:10px 12px;cursor:pointer;color:var(--text);font-family:inherit}.ds-related--static{cursor:default}.ds-related__badge{min-width:62px;text-align:center;border:1px solid currentcolor;border-radius:var(--r-small);padding:5px 4px}.ds-related__days{display:block;font-size:15px;font-weight:800}.ds-related__unit{display:block;font-size:10px;font-weight:700}.ds-related__body{flex:1;min-width:0}.ds-related__title{display:block;font-size:13px;font-weight:700}.ds-related__meta{display:block;font-size:11px;color:var(--muted);margin-top:2px}.ds-related__chev{font-size:12px;color:var(--faint)}\n"] }]
|
|
21122
21316
|
}], ctorParameters: () => [], propDecorators: { logs: [{ type: i0.Input, args: [{ isSignal: true, alias: "logs", required: false }] }], birthDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "birthDate", required: false }] }], initialDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialDate", required: false }] }], feedGroupGapMinutes: [{ type: i0.Input, args: [{ isSignal: true, alias: "feedGroupGapMinutes", required: false }] }], gender: [{ type: i0.Input, args: [{ isSignal: true, alias: "gender", required: false }] }] } });
|
|
21123
21317
|
|
|
21124
|
-
/**
|
|
21125
|
-
* Float menu full-screen — mở từ nút hamburger trên hero.
|
|
21126
|
-
* V4: profile card + settings tiles + chức năng list + About + Xoá profile.
|
|
21127
|
-
*/
|
|
21128
|
-
class SettingsMenuComponent {
|
|
21129
|
-
constructor() {
|
|
21130
|
-
this.appName = input('', ...(ngDevMode ? [{ debugName: "appName" }] : /* istanbul ignore next */ []));
|
|
21131
|
-
this.appVersion = input('', ...(ngDevMode ? [{ debugName: "appVersion" }] : /* istanbul ignore next */ []));
|
|
21132
|
-
/** Hiển thị entry Explorer (chỉ khi explorer được bật). */
|
|
21133
|
-
this.showDocuments = input(false, ...(ngDevMode ? [{ debugName: "showDocuments" }] : /* istanbul ignore next */ []));
|
|
21134
|
-
/** Số thông báo chưa đọc — hiển thị badge trên tile Thông báo. */
|
|
21135
|
-
this.unreadCount = input(0, ...(ngDevMode ? [{ debugName: "unreadCount" }] : /* istanbul ignore next */ []));
|
|
21136
|
-
/** Số món chưa mua — badge trên tile Mua sắm. */
|
|
21137
|
-
this.preparingPendingCount = input(0, ...(ngDevMode ? [{ debugName: "preparingPendingCount" }] : /* istanbul ignore next */ []));
|
|
21138
|
-
/** Tên bé — profile card + label "Chuẩn bị cho <bé> ra ngoài". */
|
|
21139
|
-
this.babyName = input('', ...(ngDevMode ? [{ debugName: "babyName" }] : /* istanbul ignore next */ []));
|
|
21140
|
-
/** Chip tuổi (vd. "2t 3w 5n") — hiển thị trên profile card. */
|
|
21141
|
-
this.ageLabel = input('', ...(ngDevMode ? [{ debugName: "ageLabel" }] : /* istanbul ignore next */ []));
|
|
21142
|
-
/** Cân nặng (vd. "5.2 kg") — hiển thị trên profile card. */
|
|
21143
|
-
this.weightLabel = input('', ...(ngDevMode ? [{ debugName: "weightLabel" }] : /* istanbul ignore next */ []));
|
|
21144
|
-
/** Ngày sinh hiển thị (vd. "01/04/2026"). */
|
|
21145
|
-
this.birthDateLabel = input('', ...(ngDevMode ? [{ debugName: "birthDateLabel" }] : /* istanbul ignore next */ []));
|
|
21146
|
-
this.close = output();
|
|
21147
|
-
this.select = output();
|
|
21148
|
-
this.initials = computed(() => {
|
|
21149
|
-
const name = this.babyName().trim();
|
|
21150
|
-
if (!name)
|
|
21151
|
-
return '?';
|
|
21152
|
-
const parts = name.split(/\s+/).filter(Boolean);
|
|
21153
|
-
if (parts.length === 1)
|
|
21154
|
-
return parts[0].slice(0, 2).toUpperCase();
|
|
21155
|
-
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
|
|
21156
|
-
}, ...(ngDevMode ? [{ debugName: "initials" }] : /* istanbul ignore next */ []));
|
|
21157
|
-
this.profileMeta = computed(() => {
|
|
21158
|
-
const bits = [this.ageLabel().trim(), this.weightLabel().trim()].filter(Boolean);
|
|
21159
|
-
return bits.join(' · ');
|
|
21160
|
-
}, ...(ngDevMode ? [{ debugName: "profileMeta" }] : /* istanbul ignore next */ []));
|
|
21161
|
-
this.goingOutLabel = computed(() => {
|
|
21162
|
-
const name = this.babyName().trim();
|
|
21163
|
-
return name ? `Chuẩn bị cho ${name} ra ngoài` : 'Chuẩn bị đi ra ngoài';
|
|
21164
|
-
}, ...(ngDevMode ? [{ debugName: "goingOutLabel" }] : /* istanbul ignore next */ []));
|
|
21165
|
-
}
|
|
21166
|
-
onSelect(id) {
|
|
21167
|
-
this.select.emit(id);
|
|
21168
|
-
}
|
|
21169
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SettingsMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
21170
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: SettingsMenuComponent, isStandalone: true, selector: "app-settings-menu", inputs: { appName: { classPropertyName: "appName", publicName: "appName", isSignal: true, isRequired: false, transformFunction: null }, appVersion: { classPropertyName: "appVersion", publicName: "appVersion", isSignal: true, isRequired: false, transformFunction: null }, showDocuments: { classPropertyName: "showDocuments", publicName: "showDocuments", isSignal: true, isRequired: false, transformFunction: null }, unreadCount: { classPropertyName: "unreadCount", publicName: "unreadCount", isSignal: true, isRequired: false, transformFunction: null }, preparingPendingCount: { classPropertyName: "preparingPendingCount", publicName: "preparingPendingCount", isSignal: true, isRequired: false, transformFunction: null }, babyName: { classPropertyName: "babyName", publicName: "babyName", isSignal: true, isRequired: false, transformFunction: null }, ageLabel: { classPropertyName: "ageLabel", publicName: "ageLabel", isSignal: true, isRequired: false, transformFunction: null }, weightLabel: { classPropertyName: "weightLabel", publicName: "weightLabel", isSignal: true, isRequired: false, transformFunction: null }, birthDateLabel: { classPropertyName: "birthDateLabel", publicName: "birthDateLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { close: "close", select: "select" }, ngImport: i0, template: "<section class=\"sm-root\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Menu\">\n <header class=\"sm-header\">\n <h1 class=\"sm-title\">{{ appName() }} d\u00E0nh cho <em>{{ babyName() }}</em></h1>\n <span class=\"sm-spacer\"></span>\n <button type=\"button\" class=\"sm-close\" (click)=\"close.emit()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"fa-solid fa-xmark\" aria-hidden=\"true\"></i>\n </button>\n </header>\n\n <div class=\"sm-body\">\n <!-- \u2500\u2500 Profile card \u2500\u2500 -->\n <button\n type=\"button\"\n class=\"sm-profile\"\n (click)=\"onSelect('edit')\"\n aria-label=\"S\u1EEDa profile b\u00E9\"\n >\n <span class=\"sm-profile__avatar\" aria-hidden=\"true\">{{ initials() }}</span>\n <span class=\"sm-profile__body\">\n <span class=\"sm-profile__name\">{{ babyName() || 'Ch\u01B0a c\u00F3 t\u00EAn' }}</span>\n @if (profileMeta()) {\n <span class=\"sm-profile__meta\">{{ profileMeta() }}</span>\n }\n @if (birthDateLabel()) {\n <span class=\"sm-profile__birth\">Sinh {{ birthDateLabel() }}</span>\n }\n </span>\n <span class=\"sm-profile__edit\">\n <i class=\"fa-solid fa-pencil\" aria-hidden=\"true\"></i>\n S\u1EEDa\n </span>\n </button>\n\n <!-- \u2500\u2500 Settings tiles \u2500\u2500 -->\n <div class=\"sm-section\">\n <div class=\"sm-section__label\">C\u00E0i \u0111\u1EB7t</div>\n <div class=\"sm-tiles\" role=\"menu\">\n <button\n type=\"button\"\n class=\"sm-tile\"\n role=\"menuitem\"\n (click)=\"onSelect('settings')\"\n >\n <span class=\"sm-tile__icon sm-tile__icon--slate\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-gear\"></i>\n </span>\n <span class=\"sm-tile__label\">C\u00E0i \u0111\u1EB7t chung</span>\n <span class=\"sm-tile__hint\">C\u00E0i \u0111\u1EB7t \u1EE9ng d\u1EE5ng</span>\n </button>\n\n <button\n type=\"button\"\n class=\"sm-tile\"\n role=\"menuitem\"\n (click)=\"onSelect('going-out-list')\"\n >\n <span class=\"sm-tile__icon sm-tile__icon--orange\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-clipboard-check\"></i>\n </span>\n <span class=\"sm-tile__label\">\u0110i ch\u01A1i</span>\n <span class=\"sm-tile__hint\">Qu\u1EA3n l\u00FD \u0111\u1ED3 \u0111\u1EA1c</span>\n </button>\n\n <button\n type=\"button\"\n class=\"sm-tile sm-tile--badge\"\n role=\"menuitem\"\n (click)=\"onSelect('preparing-purchase')\"\n >\n <span class=\"sm-tile__icon sm-tile__icon--green\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-cart-shopping\"></i>\n </span>\n <span class=\"sm-tile__label\">Mua s\u1EAFm</span>\n <span class=\"sm-tile__hint\">Checkin \u0111\u1ED3 c\u1EA7n mua</span>\n @if (preparingPendingCount() > 0) {\n <span class=\"sm-tile__badge\">{{ preparingPendingCount() }}</span>\n }\n </button>\n\n <button\n type=\"button\"\n class=\"sm-tile sm-tile--badge\"\n role=\"menuitem\"\n (click)=\"onSelect('notifications')\"\n >\n <span class=\"sm-tile__icon sm-tile__icon--pink\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-bell\"></i>\n </span>\n <span class=\"sm-tile__label\">Th\u00F4ng b\u00E1o</span>\n <span class=\"sm-tile__hint\">M\u1EDBi & \u0111\u00E3 \u0111\u1ECDc</span>\n @if (unreadCount() > 0) {\n <span class=\"sm-tile__badge\">{{ unreadCount() }}</span>\n }\n </button>\n </div>\n </div>\n\n <!-- \u2500\u2500 Features list \u2500\u2500 -->\n <div class=\"sm-section\">\n <div class=\"sm-section__label\">T\u00EDnh n\u0103ng</div>\n <nav class=\"sm-list sm-list--card\" role=\"menu\">\n <button\n type=\"button\"\n class=\"sm-item\"\n role=\"menuitem\"\n (click)=\"onSelect('daily-summary')\"\n >\n <span class=\"sm-item__well sm-item__well--cyan\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-chart-pie\"></i>\n </span>\n <span class=\"sm-item__body\">\n <span class=\"sm-item__label\">T\u1ED5ng qu\u00E1t v\u1EC1 c\u00E1c ng\u00E0y tr\u01B0\u1EDBc</span>\n <span class=\"sm-item__hint\">Th\u00F4ng tin v\u1EC1 c\u1EEF b\u00FA v\u00E0 c\u00E1c ho\u1EA1t \u0111\u1ED9ng</span>\n </span>\n <i class=\"fa-solid fa-chevron-right sm-item__chev\" aria-hidden=\"true\"></i>\n </button>\n\n <button type=\"button\" class=\"sm-item\" role=\"menuitem\" (click)=\"onSelect('weight')\">\n <span class=\"sm-item__well sm-item__well--green\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-chart-line\"></i>\n </span>\n <span class=\"sm-item__body\">\n <span class=\"sm-item__label\">Th\u1EC3 tr\u1EA1ng</span>\n <span class=\"sm-item__hint\">C\u00E2n n\u1EB7ng, chi\u1EC1u cao, t\u0103ng tr\u01B0\u1EDFng c\u1EE7a b\u00E9</span>\n </span>\n <i class=\"fa-solid fa-chevron-right sm-item__chev\" aria-hidden=\"true\"></i>\n </button>\n\n <button type=\"button\" class=\"sm-item\" role=\"menuitem\" (click)=\"onSelect('mom')\">\n <span class=\"sm-item__well sm-item__well--pink\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-person-pregnant\"></i>\n </span>\n <span class=\"sm-item__body\">\n <span class=\"sm-item__label\">G\u00F3c c\u1EE7a M\u1EB9</span>\n <span class=\"sm-item__hint\">S\u1EE9c kho\u1EBB & dinh d\u01B0\u1EE1ng</span>\n </span>\n <i class=\"fa-solid fa-chevron-right sm-item__chev\" aria-hidden=\"true\"></i>\n </button>\n\n <button\n type=\"button\"\n class=\"sm-item\"\n role=\"menuitem\"\n (click)=\"onSelect('going-out')\"\n >\n <span class=\"sm-item__well sm-item__well--orange\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-plane-departure\"></i>\n </span>\n <span class=\"sm-item__body\">\n <span class=\"sm-item__label\">{{ goingOutLabel() }}</span>\n <span class=\"sm-item__hint\">Checklist \u0111\u1ED3 \u0111i ch\u01A1i</span>\n </span>\n <i class=\"fa-solid fa-chevron-right sm-item__chev\" aria-hidden=\"true\"></i>\n </button>\n\n @if (showDocuments()) {\n <button\n type=\"button\"\n class=\"sm-item\"\n role=\"menuitem\"\n (click)=\"onSelect('documents')\"\n >\n <span class=\"sm-item__well sm-item__well--purple\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-folder\"></i>\n </span>\n <span class=\"sm-item__body\">\n <span class=\"sm-item__label\">Explorer</span>\n <span class=\"sm-item__hint\">Kho t\u00E0i li\u1EC7u c\u1EE7a b\u00E9</span>\n </span>\n <i class=\"fa-solid fa-chevron-right sm-item__chev\" aria-hidden=\"true\"></i>\n </button>\n }\n </nav>\n </div>\n\n <!-- \u2500\u2500 About \u2500\u2500 -->\n <div class=\"sm-section\">\n <div class=\"sm-section__label\">Th\u00F4ng tin v\u1EC1 \u1EE9ng d\u1EE5ng</div>\n <section class=\"sm-about\" aria-label=\"About\">\n <div class=\"sm-about__icon\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-circle-info\"></i>\n </div>\n <div class=\"sm-about__body\">\n <span class=\"sm-about__name\">{{ appName() }}</span>\n <span class=\"sm-about__version\">Phi\u00EAn b\u1EA3n {{ appVersion() }}</span>\n </div>\n </section>\n </div>\n\n <!-- \u2500\u2500 Danger \u2500\u2500 -->\n <div class=\"sm-section\">\n <div class=\"sm-section__label\">Danger Zone</div>\n <nav class=\"sm-list sm-list--card\" role=\"menu\">\n <button\n type=\"button\"\n class=\"sm-item sm-item--danger\"\n role=\"menuitem\"\n (click)=\"onSelect('delete')\"\n >\n <span class=\"sm-item__well sm-item__well--rose\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-trash\"></i>\n </span>\n <span class=\"sm-item__body\">\n <span class=\"sm-item__label\">Xo\u00E1 profile</span>\n <span class=\"sm-item__hint\">G\u1EE1 h\u1ED3 s\u01A1 b\u00E9 kh\u1ECFi thi\u1EBFt b\u1ECB n\u00E0y</span>\n </span>\n </button>\n </nav>\n </div>\n </div>\n</section>\n", styles: ["@charset \"UTF-8\";:host{--bg: #f2f3f7;--surface: #ffffff;--border: #e2e5ee;--primary: #5b6cff;--primary-soft: #eef0ff;--accent: #e0338c;--rose: #dc2626;--rose-soft: #fee2e2;--text: #0f172a;--muted: #64748b;--muted-2: #94a3b8;--cyan: #0891b2;--cyan-soft: #dff7fd;--green: #16a34a;--green-soft: #e4fbec;--pink: #e0338c;--pink-soft: #ffe8f3;--orange: #ea580c;--orange-soft: #fff0e2;--purple: #7c3aed;--purple-soft: #f2e9ff;--slate: #475569;--slate-soft: #f1f5f9;position:fixed;inset:0;z-index:210;background:var(--bg);display:flex;flex-direction:column;color:var(--text);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;animation:sm-slide-in .22s cubic-bezier(.22,1,.36,1)}@keyframes sm-slide-in{0%{transform:translate(8px);opacity:0}to{transform:translate(0);opacity:1}}.sm-root{display:flex;flex-direction:column;flex:1;min-height:0}.sm-header{flex-shrink:0;display:flex;align-items:center;gap:8px;padding:10px 14px;padding-top:calc(10px + env(safe-area-inset-top,0px));background:var(--surface);border-bottom:1px solid var(--border)}.sm-title{margin:0;font-size:17px;font-weight:700;color:var(--text)}.sm-spacer{flex:1}.sm-close{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:9px;border:none;background:var(--primary-soft);color:var(--primary);font-size:14px;cursor:pointer;-webkit-tap-highlight-color:transparent;transition:background .15s,color .15s}.sm-close:hover{filter:brightness(.97)}.sm-body{flex:1;min-height:0;overflow:auto;-webkit-overflow-scrolling:touch;padding:12px 0 calc(24px + env(safe-area-inset-bottom,0px))}.sm-profile{display:flex;align-items:center;gap:12px;width:calc(100% - 24px);margin:0 12px 14px;padding:10px 12px;border:1px solid var(--border);border-radius:16px;background:var(--surface);text-align:left;cursor:pointer;-webkit-tap-highlight-color:transparent;transition:background .15s}.sm-profile:hover{background:color-mix(in srgb,var(--primary-soft) 40%,var(--surface))}.sm-profile:active{background:var(--primary-soft)}.sm-profile__avatar{flex-shrink:0;width:40px;height:40px;border-radius:14px;background:var(--primary-soft);color:var(--primary);display:inline-flex;align-items:center;justify-content:center;font-size:15px;font-weight:800;letter-spacing:.02em}.sm-profile__body{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.sm-profile__name{font-size:15px;font-weight:700;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sm-profile__meta,.sm-profile__birth{font-size:12px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sm-profile__birth{color:var(--muted-2);font-size:11px}.sm-profile__edit{flex-shrink:0;display:inline-flex;align-items:center;gap:4px;font-size:11px;font-weight:700;color:var(--primary);background:var(--primary-soft);padding:4px 8px;border-radius:8px}.sm-profile__edit i{font-size:11px}.sm-section{margin-bottom:14px}.sm-section__label{padding:0 18px 6px;font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}.sm-tiles{margin:0 12px;display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px}.sm-tile{position:relative;display:flex;flex-direction:column;align-items:center;gap:6px;min-height:96px;padding:12px 8px 10px;border:1px solid var(--border);border-radius:14px;background:var(--surface);cursor:pointer;-webkit-tap-highlight-color:transparent;transition:background .15s}.sm-tile:hover{background:color-mix(in srgb,var(--primary-soft) 35%,var(--surface))}.sm-tile:active{background:var(--primary-soft)}.sm-tile__icon{width:38px;height:38px;border-radius:11px;display:inline-flex;align-items:center;justify-content:center;font-size:16px}.sm-tile__icon--slate{background:var(--slate-soft);color:var(--slate)}.sm-tile__icon--orange{background:var(--orange-soft);color:var(--orange)}.sm-tile__icon--pink{background:var(--pink-soft);color:var(--pink)}.sm-tile__label{font-size:11px;font-weight:700;color:var(--text);text-align:center;line-height:1.2}.sm-tile__hint{font-size:10px;color:var(--muted-2);text-align:center;line-height:1.2}.sm-tile__badge{position:absolute;top:8px;right:8px;min-width:18px;height:18px;padding:0 5px;border-radius:999px;background:var(--accent);color:#fff;font-size:10px;font-weight:700;display:inline-flex;align-items:center;justify-content:center}.sm-list{display:flex;flex-direction:column}.sm-list--card{margin:0 12px;border:1px solid var(--border);border-radius:14px;overflow:hidden;background:var(--surface)}.sm-item{display:flex;align-items:center;gap:12px;width:100%;padding:12px 14px;border:none;border-bottom:1px solid var(--border);background:transparent;text-align:left;cursor:pointer;-webkit-tap-highlight-color:transparent;transition:background .15s}.sm-item:last-child{border-bottom:none}.sm-item:hover{background:var(--primary-soft)}.sm-item:active{background:color-mix(in srgb,var(--primary-soft) 55%,var(--surface))}.sm-item__well{flex-shrink:0;width:36px;height:36px;border-radius:10px;display:inline-flex;align-items:center;justify-content:center;font-size:15px}.sm-item__well--cyan{background:var(--cyan-soft);color:var(--cyan)}.sm-item__well--green{background:var(--green-soft);color:var(--green)}.sm-item__well--pink{background:var(--pink-soft);color:var(--pink)}.sm-item__well--orange{background:var(--orange-soft);color:var(--orange)}.sm-item__well--purple{background:var(--purple-soft);color:var(--purple)}.sm-item__well--rose{background:var(--rose-soft);color:var(--rose)}.sm-item__body{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.sm-item__label{font-size:14px;font-weight:600;color:var(--text);line-height:1.25}.sm-item__hint{font-size:11px;color:var(--muted-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sm-item__chev{flex-shrink:0;font-size:13px;color:var(--muted-2)}.sm-item--danger:hover{background:var(--rose-soft)}.sm-item--danger:active{background:color-mix(in srgb,var(--rose-soft) 55%,var(--surface))}.sm-item--danger .sm-item__label{color:var(--rose)}.sm-about{display:flex;align-items:center;gap:12px;margin:0 12px;padding:12px 14px;border-radius:14px;background:var(--surface);border:1px solid var(--border)}.sm-about__icon{flex-shrink:0;width:36px;height:36px;border-radius:10px;background:var(--primary-soft);color:var(--primary);font-size:16px;display:flex;align-items:center;justify-content:center}.sm-about__body{display:flex;flex-direction:column;gap:2px;flex:1;min-width:0}.sm-about__name{font-size:14px;font-weight:700;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sm-about__version{font-size:11px;color:var(--muted)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
21171
|
-
}
|
|
21172
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SettingsMenuComponent, decorators: [{
|
|
21173
|
-
type: Component,
|
|
21174
|
-
args: [{ selector: 'app-settings-menu', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section class=\"sm-root\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Menu\">\n <header class=\"sm-header\">\n <h1 class=\"sm-title\">{{ appName() }} d\u00E0nh cho <em>{{ babyName() }}</em></h1>\n <span class=\"sm-spacer\"></span>\n <button type=\"button\" class=\"sm-close\" (click)=\"close.emit()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"fa-solid fa-xmark\" aria-hidden=\"true\"></i>\n </button>\n </header>\n\n <div class=\"sm-body\">\n <!-- \u2500\u2500 Profile card \u2500\u2500 -->\n <button\n type=\"button\"\n class=\"sm-profile\"\n (click)=\"onSelect('edit')\"\n aria-label=\"S\u1EEDa profile b\u00E9\"\n >\n <span class=\"sm-profile__avatar\" aria-hidden=\"true\">{{ initials() }}</span>\n <span class=\"sm-profile__body\">\n <span class=\"sm-profile__name\">{{ babyName() || 'Ch\u01B0a c\u00F3 t\u00EAn' }}</span>\n @if (profileMeta()) {\n <span class=\"sm-profile__meta\">{{ profileMeta() }}</span>\n }\n @if (birthDateLabel()) {\n <span class=\"sm-profile__birth\">Sinh {{ birthDateLabel() }}</span>\n }\n </span>\n <span class=\"sm-profile__edit\">\n <i class=\"fa-solid fa-pencil\" aria-hidden=\"true\"></i>\n S\u1EEDa\n </span>\n </button>\n\n <!-- \u2500\u2500 Settings tiles \u2500\u2500 -->\n <div class=\"sm-section\">\n <div class=\"sm-section__label\">C\u00E0i \u0111\u1EB7t</div>\n <div class=\"sm-tiles\" role=\"menu\">\n <button\n type=\"button\"\n class=\"sm-tile\"\n role=\"menuitem\"\n (click)=\"onSelect('settings')\"\n >\n <span class=\"sm-tile__icon sm-tile__icon--slate\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-gear\"></i>\n </span>\n <span class=\"sm-tile__label\">C\u00E0i \u0111\u1EB7t chung</span>\n <span class=\"sm-tile__hint\">C\u00E0i \u0111\u1EB7t \u1EE9ng d\u1EE5ng</span>\n </button>\n\n <button\n type=\"button\"\n class=\"sm-tile\"\n role=\"menuitem\"\n (click)=\"onSelect('going-out-list')\"\n >\n <span class=\"sm-tile__icon sm-tile__icon--orange\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-clipboard-check\"></i>\n </span>\n <span class=\"sm-tile__label\">\u0110i ch\u01A1i</span>\n <span class=\"sm-tile__hint\">Qu\u1EA3n l\u00FD \u0111\u1ED3 \u0111\u1EA1c</span>\n </button>\n\n <button\n type=\"button\"\n class=\"sm-tile sm-tile--badge\"\n role=\"menuitem\"\n (click)=\"onSelect('preparing-purchase')\"\n >\n <span class=\"sm-tile__icon sm-tile__icon--green\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-cart-shopping\"></i>\n </span>\n <span class=\"sm-tile__label\">Mua s\u1EAFm</span>\n <span class=\"sm-tile__hint\">Checkin \u0111\u1ED3 c\u1EA7n mua</span>\n @if (preparingPendingCount() > 0) {\n <span class=\"sm-tile__badge\">{{ preparingPendingCount() }}</span>\n }\n </button>\n\n <button\n type=\"button\"\n class=\"sm-tile sm-tile--badge\"\n role=\"menuitem\"\n (click)=\"onSelect('notifications')\"\n >\n <span class=\"sm-tile__icon sm-tile__icon--pink\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-bell\"></i>\n </span>\n <span class=\"sm-tile__label\">Th\u00F4ng b\u00E1o</span>\n <span class=\"sm-tile__hint\">M\u1EDBi & \u0111\u00E3 \u0111\u1ECDc</span>\n @if (unreadCount() > 0) {\n <span class=\"sm-tile__badge\">{{ unreadCount() }}</span>\n }\n </button>\n </div>\n </div>\n\n <!-- \u2500\u2500 Features list \u2500\u2500 -->\n <div class=\"sm-section\">\n <div class=\"sm-section__label\">T\u00EDnh n\u0103ng</div>\n <nav class=\"sm-list sm-list--card\" role=\"menu\">\n <button\n type=\"button\"\n class=\"sm-item\"\n role=\"menuitem\"\n (click)=\"onSelect('daily-summary')\"\n >\n <span class=\"sm-item__well sm-item__well--cyan\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-chart-pie\"></i>\n </span>\n <span class=\"sm-item__body\">\n <span class=\"sm-item__label\">T\u1ED5ng qu\u00E1t v\u1EC1 c\u00E1c ng\u00E0y tr\u01B0\u1EDBc</span>\n <span class=\"sm-item__hint\">Th\u00F4ng tin v\u1EC1 c\u1EEF b\u00FA v\u00E0 c\u00E1c ho\u1EA1t \u0111\u1ED9ng</span>\n </span>\n <i class=\"fa-solid fa-chevron-right sm-item__chev\" aria-hidden=\"true\"></i>\n </button>\n\n <button type=\"button\" class=\"sm-item\" role=\"menuitem\" (click)=\"onSelect('weight')\">\n <span class=\"sm-item__well sm-item__well--green\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-chart-line\"></i>\n </span>\n <span class=\"sm-item__body\">\n <span class=\"sm-item__label\">Th\u1EC3 tr\u1EA1ng</span>\n <span class=\"sm-item__hint\">C\u00E2n n\u1EB7ng, chi\u1EC1u cao, t\u0103ng tr\u01B0\u1EDFng c\u1EE7a b\u00E9</span>\n </span>\n <i class=\"fa-solid fa-chevron-right sm-item__chev\" aria-hidden=\"true\"></i>\n </button>\n\n <button type=\"button\" class=\"sm-item\" role=\"menuitem\" (click)=\"onSelect('mom')\">\n <span class=\"sm-item__well sm-item__well--pink\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-person-pregnant\"></i>\n </span>\n <span class=\"sm-item__body\">\n <span class=\"sm-item__label\">G\u00F3c c\u1EE7a M\u1EB9</span>\n <span class=\"sm-item__hint\">S\u1EE9c kho\u1EBB & dinh d\u01B0\u1EE1ng</span>\n </span>\n <i class=\"fa-solid fa-chevron-right sm-item__chev\" aria-hidden=\"true\"></i>\n </button>\n\n <button\n type=\"button\"\n class=\"sm-item\"\n role=\"menuitem\"\n (click)=\"onSelect('going-out')\"\n >\n <span class=\"sm-item__well sm-item__well--orange\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-plane-departure\"></i>\n </span>\n <span class=\"sm-item__body\">\n <span class=\"sm-item__label\">{{ goingOutLabel() }}</span>\n <span class=\"sm-item__hint\">Checklist \u0111\u1ED3 \u0111i ch\u01A1i</span>\n </span>\n <i class=\"fa-solid fa-chevron-right sm-item__chev\" aria-hidden=\"true\"></i>\n </button>\n\n @if (showDocuments()) {\n <button\n type=\"button\"\n class=\"sm-item\"\n role=\"menuitem\"\n (click)=\"onSelect('documents')\"\n >\n <span class=\"sm-item__well sm-item__well--purple\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-folder\"></i>\n </span>\n <span class=\"sm-item__body\">\n <span class=\"sm-item__label\">Explorer</span>\n <span class=\"sm-item__hint\">Kho t\u00E0i li\u1EC7u c\u1EE7a b\u00E9</span>\n </span>\n <i class=\"fa-solid fa-chevron-right sm-item__chev\" aria-hidden=\"true\"></i>\n </button>\n }\n </nav>\n </div>\n\n <!-- \u2500\u2500 About \u2500\u2500 -->\n <div class=\"sm-section\">\n <div class=\"sm-section__label\">Th\u00F4ng tin v\u1EC1 \u1EE9ng d\u1EE5ng</div>\n <section class=\"sm-about\" aria-label=\"About\">\n <div class=\"sm-about__icon\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-circle-info\"></i>\n </div>\n <div class=\"sm-about__body\">\n <span class=\"sm-about__name\">{{ appName() }}</span>\n <span class=\"sm-about__version\">Phi\u00EAn b\u1EA3n {{ appVersion() }}</span>\n </div>\n </section>\n </div>\n\n <!-- \u2500\u2500 Danger \u2500\u2500 -->\n <div class=\"sm-section\">\n <div class=\"sm-section__label\">Danger Zone</div>\n <nav class=\"sm-list sm-list--card\" role=\"menu\">\n <button\n type=\"button\"\n class=\"sm-item sm-item--danger\"\n role=\"menuitem\"\n (click)=\"onSelect('delete')\"\n >\n <span class=\"sm-item__well sm-item__well--rose\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-trash\"></i>\n </span>\n <span class=\"sm-item__body\">\n <span class=\"sm-item__label\">Xo\u00E1 profile</span>\n <span class=\"sm-item__hint\">G\u1EE1 h\u1ED3 s\u01A1 b\u00E9 kh\u1ECFi thi\u1EBFt b\u1ECB n\u00E0y</span>\n </span>\n </button>\n </nav>\n </div>\n </div>\n</section>\n", styles: ["@charset \"UTF-8\";:host{--bg: #f2f3f7;--surface: #ffffff;--border: #e2e5ee;--primary: #5b6cff;--primary-soft: #eef0ff;--accent: #e0338c;--rose: #dc2626;--rose-soft: #fee2e2;--text: #0f172a;--muted: #64748b;--muted-2: #94a3b8;--cyan: #0891b2;--cyan-soft: #dff7fd;--green: #16a34a;--green-soft: #e4fbec;--pink: #e0338c;--pink-soft: #ffe8f3;--orange: #ea580c;--orange-soft: #fff0e2;--purple: #7c3aed;--purple-soft: #f2e9ff;--slate: #475569;--slate-soft: #f1f5f9;position:fixed;inset:0;z-index:210;background:var(--bg);display:flex;flex-direction:column;color:var(--text);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;animation:sm-slide-in .22s cubic-bezier(.22,1,.36,1)}@keyframes sm-slide-in{0%{transform:translate(8px);opacity:0}to{transform:translate(0);opacity:1}}.sm-root{display:flex;flex-direction:column;flex:1;min-height:0}.sm-header{flex-shrink:0;display:flex;align-items:center;gap:8px;padding:10px 14px;padding-top:calc(10px + env(safe-area-inset-top,0px));background:var(--surface);border-bottom:1px solid var(--border)}.sm-title{margin:0;font-size:17px;font-weight:700;color:var(--text)}.sm-spacer{flex:1}.sm-close{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:9px;border:none;background:var(--primary-soft);color:var(--primary);font-size:14px;cursor:pointer;-webkit-tap-highlight-color:transparent;transition:background .15s,color .15s}.sm-close:hover{filter:brightness(.97)}.sm-body{flex:1;min-height:0;overflow:auto;-webkit-overflow-scrolling:touch;padding:12px 0 calc(24px + env(safe-area-inset-bottom,0px))}.sm-profile{display:flex;align-items:center;gap:12px;width:calc(100% - 24px);margin:0 12px 14px;padding:10px 12px;border:1px solid var(--border);border-radius:16px;background:var(--surface);text-align:left;cursor:pointer;-webkit-tap-highlight-color:transparent;transition:background .15s}.sm-profile:hover{background:color-mix(in srgb,var(--primary-soft) 40%,var(--surface))}.sm-profile:active{background:var(--primary-soft)}.sm-profile__avatar{flex-shrink:0;width:40px;height:40px;border-radius:14px;background:var(--primary-soft);color:var(--primary);display:inline-flex;align-items:center;justify-content:center;font-size:15px;font-weight:800;letter-spacing:.02em}.sm-profile__body{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.sm-profile__name{font-size:15px;font-weight:700;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sm-profile__meta,.sm-profile__birth{font-size:12px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sm-profile__birth{color:var(--muted-2);font-size:11px}.sm-profile__edit{flex-shrink:0;display:inline-flex;align-items:center;gap:4px;font-size:11px;font-weight:700;color:var(--primary);background:var(--primary-soft);padding:4px 8px;border-radius:8px}.sm-profile__edit i{font-size:11px}.sm-section{margin-bottom:14px}.sm-section__label{padding:0 18px 6px;font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}.sm-tiles{margin:0 12px;display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px}.sm-tile{position:relative;display:flex;flex-direction:column;align-items:center;gap:6px;min-height:96px;padding:12px 8px 10px;border:1px solid var(--border);border-radius:14px;background:var(--surface);cursor:pointer;-webkit-tap-highlight-color:transparent;transition:background .15s}.sm-tile:hover{background:color-mix(in srgb,var(--primary-soft) 35%,var(--surface))}.sm-tile:active{background:var(--primary-soft)}.sm-tile__icon{width:38px;height:38px;border-radius:11px;display:inline-flex;align-items:center;justify-content:center;font-size:16px}.sm-tile__icon--slate{background:var(--slate-soft);color:var(--slate)}.sm-tile__icon--orange{background:var(--orange-soft);color:var(--orange)}.sm-tile__icon--pink{background:var(--pink-soft);color:var(--pink)}.sm-tile__label{font-size:11px;font-weight:700;color:var(--text);text-align:center;line-height:1.2}.sm-tile__hint{font-size:10px;color:var(--muted-2);text-align:center;line-height:1.2}.sm-tile__badge{position:absolute;top:8px;right:8px;min-width:18px;height:18px;padding:0 5px;border-radius:999px;background:var(--accent);color:#fff;font-size:10px;font-weight:700;display:inline-flex;align-items:center;justify-content:center}.sm-list{display:flex;flex-direction:column}.sm-list--card{margin:0 12px;border:1px solid var(--border);border-radius:14px;overflow:hidden;background:var(--surface)}.sm-item{display:flex;align-items:center;gap:12px;width:100%;padding:12px 14px;border:none;border-bottom:1px solid var(--border);background:transparent;text-align:left;cursor:pointer;-webkit-tap-highlight-color:transparent;transition:background .15s}.sm-item:last-child{border-bottom:none}.sm-item:hover{background:var(--primary-soft)}.sm-item:active{background:color-mix(in srgb,var(--primary-soft) 55%,var(--surface))}.sm-item__well{flex-shrink:0;width:36px;height:36px;border-radius:10px;display:inline-flex;align-items:center;justify-content:center;font-size:15px}.sm-item__well--cyan{background:var(--cyan-soft);color:var(--cyan)}.sm-item__well--green{background:var(--green-soft);color:var(--green)}.sm-item__well--pink{background:var(--pink-soft);color:var(--pink)}.sm-item__well--orange{background:var(--orange-soft);color:var(--orange)}.sm-item__well--purple{background:var(--purple-soft);color:var(--purple)}.sm-item__well--rose{background:var(--rose-soft);color:var(--rose)}.sm-item__body{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.sm-item__label{font-size:14px;font-weight:600;color:var(--text);line-height:1.25}.sm-item__hint{font-size:11px;color:var(--muted-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sm-item__chev{flex-shrink:0;font-size:13px;color:var(--muted-2)}.sm-item--danger:hover{background:var(--rose-soft)}.sm-item--danger:active{background:color-mix(in srgb,var(--rose-soft) 55%,var(--surface))}.sm-item--danger .sm-item__label{color:var(--rose)}.sm-about{display:flex;align-items:center;gap:12px;margin:0 12px;padding:12px 14px;border-radius:14px;background:var(--surface);border:1px solid var(--border)}.sm-about__icon{flex-shrink:0;width:36px;height:36px;border-radius:10px;background:var(--primary-soft);color:var(--primary);font-size:16px;display:flex;align-items:center;justify-content:center}.sm-about__body{display:flex;flex-direction:column;gap:2px;flex:1;min-width:0}.sm-about__name{font-size:14px;font-weight:700;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sm-about__version{font-size:11px;color:var(--muted)}\n"] }]
|
|
21175
|
-
}], propDecorators: { appName: [{ type: i0.Input, args: [{ isSignal: true, alias: "appName", required: false }] }], appVersion: [{ type: i0.Input, args: [{ isSignal: true, alias: "appVersion", required: false }] }], showDocuments: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDocuments", required: false }] }], unreadCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "unreadCount", required: false }] }], preparingPendingCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "preparingPendingCount", required: false }] }], babyName: [{ type: i0.Input, args: [{ isSignal: true, alias: "babyName", required: false }] }], ageLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ageLabel", required: false }] }], weightLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "weightLabel", required: false }] }], birthDateLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "birthDateLabel", required: false }] }], close: [{ type: i0.Output, args: ["close"] }], select: [{ type: i0.Output, args: ["select"] }] } });
|
|
21176
|
-
|
|
21177
21318
|
const STORAGE_KEY = 'going-out-checklist::v2';
|
|
21178
21319
|
const DEFAULT_SETTINGS = { allowEdit: true, allowAdd: true, allowPartial: true };
|
|
21179
21320
|
function toTrip(items) {
|
|
@@ -21232,6 +21373,16 @@ class GoingOutChecklistService {
|
|
|
21232
21373
|
this.notNeededCount = computed(() => this.trip().filter((i) => i.notNeeded).length, ...(ngDevMode ? [{ debugName: "notNeededCount" }] : /* istanbul ignore next */ []));
|
|
21233
21374
|
this.total = computed(() => this.trip().length, ...(ngDevMode ? [{ debugName: "total" }] : /* istanbul ignore next */ []));
|
|
21234
21375
|
this.allDone = computed(() => this.trip().length > 0 && this.trip().every((i) => isTripSatisfied(i)), ...(ngDevMode ? [{ debugName: "allDone" }] : /* istanbul ignore next */ []));
|
|
21376
|
+
/** Món cần mang lần này (không tính notNeeded). */
|
|
21377
|
+
this.activeTripCount = computed(() => this.trip().filter((i) => !i.notNeeded).length, ...(ngDevMode ? [{ debugName: "activeTripCount" }] : /* istanbul ignore next */ []));
|
|
21378
|
+
/** Đã gói xong (status done, vẫn đang mang). */
|
|
21379
|
+
this.packedCount = computed(() => this.trip().filter((i) => !i.notNeeded && i.status === 'done').length, ...(ngDevMode ? [{ debugName: "packedCount" }] : /* istanbul ignore next */ []));
|
|
21380
|
+
this.progressPercent = computed(() => {
|
|
21381
|
+
const total = this.activeTripCount();
|
|
21382
|
+
if (!total)
|
|
21383
|
+
return 0;
|
|
21384
|
+
return Math.round((this.packedCount() / total) * 100);
|
|
21385
|
+
}, ...(ngDevMode ? [{ debugName: "progressPercent" }] : /* istanbul ignore next */ []));
|
|
21235
21386
|
this.allowEdit = computed(() => this.settings().allowEdit, ...(ngDevMode ? [{ debugName: "allowEdit" }] : /* istanbul ignore next */ []));
|
|
21236
21387
|
this.allowAdd = computed(() => this.settings().allowAdd, ...(ngDevMode ? [{ debugName: "allowAdd" }] : /* istanbul ignore next */ []));
|
|
21237
21388
|
this.allowPartial = computed(() => this.settings().allowPartial, ...(ngDevMode ? [{ debugName: "allowPartial" }] : /* istanbul ignore next */ []));
|
|
@@ -21272,7 +21423,8 @@ class GoingOutChecklistService {
|
|
|
21272
21423
|
title: withDetails ? patch.title.trim() || it.title : it.title,
|
|
21273
21424
|
desc: withDetails ? patch.desc.trim() : it.desc,
|
|
21274
21425
|
note: patch.note.trim(),
|
|
21275
|
-
|
|
21426
|
+
notNeeded: patch.notNeeded,
|
|
21427
|
+
status: patch.notNeeded ? 'none' : patch.status,
|
|
21276
21428
|
}
|
|
21277
21429
|
: it));
|
|
21278
21430
|
}
|
|
@@ -21280,6 +21432,35 @@ class GoingOutChecklistService {
|
|
|
21280
21432
|
removeFromTrip(id) {
|
|
21281
21433
|
this.trip.update((rows) => rows.filter((it) => it.id !== id));
|
|
21282
21434
|
}
|
|
21435
|
+
/** Đổi thứ tự checklist chuyến — chỉ localStorage, không ghi Sheet. */
|
|
21436
|
+
moveTripItem(index, delta) {
|
|
21437
|
+
if (!delta)
|
|
21438
|
+
return;
|
|
21439
|
+
this.trip.update((rows) => {
|
|
21440
|
+
const to = index + delta;
|
|
21441
|
+
if (to < 0 || to >= rows.length)
|
|
21442
|
+
return rows;
|
|
21443
|
+
const next = [...rows];
|
|
21444
|
+
const tmp = next[index];
|
|
21445
|
+
next[index] = next[to];
|
|
21446
|
+
next[to] = tmp;
|
|
21447
|
+
return next;
|
|
21448
|
+
});
|
|
21449
|
+
}
|
|
21450
|
+
reorderTrip(draggedId, targetId) {
|
|
21451
|
+
if (draggedId === targetId)
|
|
21452
|
+
return;
|
|
21453
|
+
this.trip.update((rows) => {
|
|
21454
|
+
const from = rows.findIndex((r) => r.id === draggedId);
|
|
21455
|
+
const to = rows.findIndex((r) => r.id === targetId);
|
|
21456
|
+
if (from < 0 || to < 0)
|
|
21457
|
+
return rows;
|
|
21458
|
+
const next = [...rows];
|
|
21459
|
+
const [moved] = next.splice(from, 1);
|
|
21460
|
+
next.splice(to, 0, moved);
|
|
21461
|
+
return next;
|
|
21462
|
+
});
|
|
21463
|
+
}
|
|
21283
21464
|
/** Khôi phục checklist theo danh sách mẫu: tick, ghi chú, món đã ẩn / thêm tạm. */
|
|
21284
21465
|
resetTrip() {
|
|
21285
21466
|
this.trip.set(toTrip(this.items()));
|
|
@@ -21305,18 +21486,21 @@ class GoingOutChecklistService {
|
|
|
21305
21486
|
next: () => {
|
|
21306
21487
|
this.sheetSyncError.set('');
|
|
21307
21488
|
this.bootstrap.invalidate('goingOutItems');
|
|
21308
|
-
this.bootstrap.invalidate('preparingProducts');
|
|
21309
21489
|
this.reloadFromSheet().subscribe();
|
|
21310
21490
|
},
|
|
21311
21491
|
error: () => this.rollbackMasterAdd(item.id),
|
|
21312
21492
|
});
|
|
21313
21493
|
}
|
|
21314
|
-
addChecklistItem(title, desc, note, status) {
|
|
21494
|
+
addChecklistItem(title, desc, note, status, notNeeded = false) {
|
|
21315
21495
|
const t = title.trim();
|
|
21316
21496
|
if (!t)
|
|
21317
21497
|
return;
|
|
21318
21498
|
const item = { id: genId(), title: t, desc: desc.trim() };
|
|
21319
|
-
|
|
21499
|
+
const tripStatus = notNeeded ? 'none' : status;
|
|
21500
|
+
this.trip.update((rows) => [
|
|
21501
|
+
...rows,
|
|
21502
|
+
{ ...item, note: note.trim(), status: tripStatus, notNeeded },
|
|
21503
|
+
]);
|
|
21320
21504
|
}
|
|
21321
21505
|
updateMaster(id, title, desc) {
|
|
21322
21506
|
const t = title.trim();
|
|
@@ -21333,7 +21517,6 @@ class GoingOutChecklistService {
|
|
|
21333
21517
|
next: () => {
|
|
21334
21518
|
this.sheetSyncError.set('');
|
|
21335
21519
|
this.bootstrap.invalidate('goingOutItems');
|
|
21336
|
-
this.bootstrap.invalidate('preparingProducts');
|
|
21337
21520
|
this.reloadFromSheet().subscribe();
|
|
21338
21521
|
},
|
|
21339
21522
|
error: () => {
|
|
@@ -21357,7 +21540,6 @@ class GoingOutChecklistService {
|
|
|
21357
21540
|
next: () => {
|
|
21358
21541
|
this.sheetSyncError.set('');
|
|
21359
21542
|
this.bootstrap.invalidate('goingOutItems');
|
|
21360
|
-
this.bootstrap.invalidate('preparingProducts');
|
|
21361
21543
|
this.reloadFromSheet().subscribe();
|
|
21362
21544
|
},
|
|
21363
21545
|
error: () => {
|
|
@@ -21509,6 +21691,71 @@ function normalizeTrip(item) {
|
|
|
21509
21691
|
return { ...item, notNeeded: Boolean(item.notNeeded) };
|
|
21510
21692
|
}
|
|
21511
21693
|
|
|
21694
|
+
/**
|
|
21695
|
+
* Float menu full-screen — mở từ nút hamburger trên hero.
|
|
21696
|
+
* V4: profile card + settings tiles + chức năng list + About + Xoá profile.
|
|
21697
|
+
*/
|
|
21698
|
+
class SettingsMenuComponent {
|
|
21699
|
+
constructor() {
|
|
21700
|
+
this.goingOut = inject(GoingOutChecklistService);
|
|
21701
|
+
this.appName = input('', ...(ngDevMode ? [{ debugName: "appName" }] : /* istanbul ignore next */ []));
|
|
21702
|
+
this.appVersion = input('', ...(ngDevMode ? [{ debugName: "appVersion" }] : /* istanbul ignore next */ []));
|
|
21703
|
+
/** Hiển thị entry Explorer (chỉ khi explorer được bật). */
|
|
21704
|
+
this.showDocuments = input(false, ...(ngDevMode ? [{ debugName: "showDocuments" }] : /* istanbul ignore next */ []));
|
|
21705
|
+
/** Số thông báo chưa đọc — hiển thị badge trên tile Thông báo. */
|
|
21706
|
+
this.unreadCount = input(0, ...(ngDevMode ? [{ debugName: "unreadCount" }] : /* istanbul ignore next */ []));
|
|
21707
|
+
/** Số món chưa mua — badge trên tile Mua sắm. */
|
|
21708
|
+
this.preparingPendingCount = input(0, ...(ngDevMode ? [{ debugName: "preparingPendingCount" }] : /* istanbul ignore next */ []));
|
|
21709
|
+
/** Tên bé — profile card + label "Chuẩn bị cho <bé> ra ngoài". */
|
|
21710
|
+
this.babyName = input('', ...(ngDevMode ? [{ debugName: "babyName" }] : /* istanbul ignore next */ []));
|
|
21711
|
+
/** Chip tuổi (vd. "2t 3w 5n") — hiển thị trên profile card. */
|
|
21712
|
+
this.ageLabel = input('', ...(ngDevMode ? [{ debugName: "ageLabel" }] : /* istanbul ignore next */ []));
|
|
21713
|
+
/** Cân nặng (vd. "5.2 kg") — hiển thị trên profile card. */
|
|
21714
|
+
this.weightLabel = input('', ...(ngDevMode ? [{ debugName: "weightLabel" }] : /* istanbul ignore next */ []));
|
|
21715
|
+
/** Ngày sinh hiển thị (vd. "01/04/2026"). */
|
|
21716
|
+
this.birthDateLabel = input('', ...(ngDevMode ? [{ debugName: "birthDateLabel" }] : /* istanbul ignore next */ []));
|
|
21717
|
+
this.close = output();
|
|
21718
|
+
this.select = output();
|
|
21719
|
+
this.initials = computed(() => {
|
|
21720
|
+
const name = this.babyName().trim();
|
|
21721
|
+
if (!name)
|
|
21722
|
+
return '?';
|
|
21723
|
+
const parts = name.split(/\s+/).filter(Boolean);
|
|
21724
|
+
if (parts.length === 1)
|
|
21725
|
+
return parts[0].slice(0, 2).toUpperCase();
|
|
21726
|
+
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
|
|
21727
|
+
}, ...(ngDevMode ? [{ debugName: "initials" }] : /* istanbul ignore next */ []));
|
|
21728
|
+
this.profileMeta = computed(() => {
|
|
21729
|
+
const bits = [this.ageLabel().trim(), this.weightLabel().trim()].filter(Boolean);
|
|
21730
|
+
return bits.join(' · ');
|
|
21731
|
+
}, ...(ngDevMode ? [{ debugName: "profileMeta" }] : /* istanbul ignore next */ []));
|
|
21732
|
+
this.goingOutLabel = computed(() => {
|
|
21733
|
+
const name = this.babyName().trim();
|
|
21734
|
+
return name ? `Chuẩn bị cho ${name} ra ngoài` : 'Chuẩn bị đi ra ngoài';
|
|
21735
|
+
}, ...(ngDevMode ? [{ debugName: "goingOutLabel" }] : /* istanbul ignore next */ []));
|
|
21736
|
+
this.goingOutHint = computed(() => {
|
|
21737
|
+
const trip = this.goingOut.trip();
|
|
21738
|
+
if (!trip.length)
|
|
21739
|
+
return 'Checklist đồ đi chơi';
|
|
21740
|
+
const active = trip.filter((i) => !i.notNeeded);
|
|
21741
|
+
const packed = active.filter((i) => i.status === 'done').length;
|
|
21742
|
+
const total = active.length;
|
|
21743
|
+
if (!total)
|
|
21744
|
+
return 'Không mang món nào lần này';
|
|
21745
|
+
return `${packed}/${total} sẵn sàng · ${this.goingOut.progressPercent()}%`;
|
|
21746
|
+
}, ...(ngDevMode ? [{ debugName: "goingOutHint" }] : /* istanbul ignore next */ []));
|
|
21747
|
+
}
|
|
21748
|
+
onSelect(id) {
|
|
21749
|
+
this.select.emit(id);
|
|
21750
|
+
}
|
|
21751
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SettingsMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
21752
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: SettingsMenuComponent, isStandalone: true, selector: "app-settings-menu", inputs: { appName: { classPropertyName: "appName", publicName: "appName", isSignal: true, isRequired: false, transformFunction: null }, appVersion: { classPropertyName: "appVersion", publicName: "appVersion", isSignal: true, isRequired: false, transformFunction: null }, showDocuments: { classPropertyName: "showDocuments", publicName: "showDocuments", isSignal: true, isRequired: false, transformFunction: null }, unreadCount: { classPropertyName: "unreadCount", publicName: "unreadCount", isSignal: true, isRequired: false, transformFunction: null }, preparingPendingCount: { classPropertyName: "preparingPendingCount", publicName: "preparingPendingCount", isSignal: true, isRequired: false, transformFunction: null }, babyName: { classPropertyName: "babyName", publicName: "babyName", isSignal: true, isRequired: false, transformFunction: null }, ageLabel: { classPropertyName: "ageLabel", publicName: "ageLabel", isSignal: true, isRequired: false, transformFunction: null }, weightLabel: { classPropertyName: "weightLabel", publicName: "weightLabel", isSignal: true, isRequired: false, transformFunction: null }, birthDateLabel: { classPropertyName: "birthDateLabel", publicName: "birthDateLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { close: "close", select: "select" }, ngImport: i0, template: "<section class=\"sm-root\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Menu\">\n <header class=\"sm-header\">\n <h1 class=\"sm-title\">{{ appName() }} d\u00E0nh cho <em>{{ babyName() }}</em></h1>\n <span class=\"sm-spacer\"></span>\n <button type=\"button\" class=\"sm-close\" (click)=\"close.emit()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"fa-solid fa-xmark\" aria-hidden=\"true\"></i>\n </button>\n </header>\n\n <div class=\"sm-body\">\n <!-- \u2500\u2500 Profile card \u2500\u2500 -->\n <button\n type=\"button\"\n class=\"sm-profile\"\n (click)=\"onSelect('edit')\"\n aria-label=\"S\u1EEDa profile b\u00E9\"\n >\n <span class=\"sm-profile__avatar\" aria-hidden=\"true\">{{ initials() }}</span>\n <span class=\"sm-profile__body\">\n <span class=\"sm-profile__name\">{{ babyName() || 'Ch\u01B0a c\u00F3 t\u00EAn' }}</span>\n @if (profileMeta()) {\n <span class=\"sm-profile__meta\">{{ profileMeta() }}</span>\n }\n @if (birthDateLabel()) {\n <span class=\"sm-profile__birth\">Sinh {{ birthDateLabel() }}</span>\n }\n </span>\n <span class=\"sm-profile__edit\">\n <i class=\"fa-solid fa-pencil\" aria-hidden=\"true\"></i>\n S\u1EEDa\n </span>\n </button>\n\n <!-- \u2500\u2500 Settings tiles \u2500\u2500 -->\n <div class=\"sm-section\">\n <div class=\"sm-section__label\">C\u00E0i \u0111\u1EB7t</div>\n <div class=\"sm-tiles\" role=\"menu\">\n <button\n type=\"button\"\n class=\"sm-tile\"\n role=\"menuitem\"\n (click)=\"onSelect('settings')\"\n >\n <span class=\"sm-tile__icon sm-tile__icon--slate\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-gear\"></i>\n </span>\n <span class=\"sm-tile__label\">C\u00E0i \u0111\u1EB7t chung</span>\n <span class=\"sm-tile__hint\">C\u00E0i \u0111\u1EB7t \u1EE9ng d\u1EE5ng</span>\n </button>\n\n <button\n type=\"button\"\n class=\"sm-tile\"\n role=\"menuitem\"\n (click)=\"onSelect('going-out-list')\"\n >\n <span class=\"sm-tile__icon sm-tile__icon--orange\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-clipboard-check\"></i>\n </span>\n <span class=\"sm-tile__label\">\u0110i ch\u01A1i</span>\n <span class=\"sm-tile__hint\">Qu\u1EA3n l\u00FD \u0111\u1ED3 \u0111\u1EA1c</span>\n </button>\n\n <button\n type=\"button\"\n class=\"sm-tile sm-tile--badge\"\n role=\"menuitem\"\n (click)=\"onSelect('preparing-purchase')\"\n >\n <span class=\"sm-tile__icon sm-tile__icon--green\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-cart-shopping\"></i>\n </span>\n <span class=\"sm-tile__label\">Mua s\u1EAFm</span>\n <span class=\"sm-tile__hint\">Checkin \u0111\u1ED3 c\u1EA7n mua</span>\n @if (preparingPendingCount() > 0) {\n <span class=\"sm-tile__badge\">{{ preparingPendingCount() }}</span>\n }\n </button>\n\n <button\n type=\"button\"\n class=\"sm-tile sm-tile--badge\"\n role=\"menuitem\"\n (click)=\"onSelect('notifications')\"\n >\n <span class=\"sm-tile__icon sm-tile__icon--pink\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-bell\"></i>\n </span>\n <span class=\"sm-tile__label\">Th\u00F4ng b\u00E1o</span>\n <span class=\"sm-tile__hint\">M\u1EDBi & \u0111\u00E3 \u0111\u1ECDc</span>\n @if (unreadCount() > 0) {\n <span class=\"sm-tile__badge\">{{ unreadCount() }}</span>\n }\n </button>\n </div>\n </div>\n\n <!-- \u2500\u2500 Features list \u2500\u2500 -->\n <div class=\"sm-section\">\n <div class=\"sm-section__label\">T\u00EDnh n\u0103ng</div>\n <nav class=\"sm-list sm-list--card\" role=\"menu\">\n <button\n type=\"button\"\n class=\"sm-item\"\n role=\"menuitem\"\n (click)=\"onSelect('daily-summary')\"\n >\n <span class=\"sm-item__well sm-item__well--cyan\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-chart-pie\"></i>\n </span>\n <span class=\"sm-item__body\">\n <span class=\"sm-item__label\">T\u1ED5ng qu\u00E1t v\u1EC1 c\u00E1c ng\u00E0y tr\u01B0\u1EDBc</span>\n <span class=\"sm-item__hint\">Th\u00F4ng tin v\u1EC1 c\u1EEF b\u00FA v\u00E0 c\u00E1c ho\u1EA1t \u0111\u1ED9ng</span>\n </span>\n <i class=\"fa-solid fa-chevron-right sm-item__chev\" aria-hidden=\"true\"></i>\n </button>\n\n <button type=\"button\" class=\"sm-item\" role=\"menuitem\" (click)=\"onSelect('weight')\">\n <span class=\"sm-item__well sm-item__well--green\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-chart-line\"></i>\n </span>\n <span class=\"sm-item__body\">\n <span class=\"sm-item__label\">Th\u1EC3 tr\u1EA1ng</span>\n <span class=\"sm-item__hint\">C\u00E2n n\u1EB7ng, chi\u1EC1u cao, t\u0103ng tr\u01B0\u1EDFng c\u1EE7a b\u00E9</span>\n </span>\n <i class=\"fa-solid fa-chevron-right sm-item__chev\" aria-hidden=\"true\"></i>\n </button>\n\n <button type=\"button\" class=\"sm-item\" role=\"menuitem\" (click)=\"onSelect('mom')\">\n <span class=\"sm-item__well sm-item__well--pink\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-person-pregnant\"></i>\n </span>\n <span class=\"sm-item__body\">\n <span class=\"sm-item__label\">G\u00F3c c\u1EE7a M\u1EB9</span>\n <span class=\"sm-item__hint\">S\u1EE9c kho\u1EBB & dinh d\u01B0\u1EE1ng</span>\n </span>\n <i class=\"fa-solid fa-chevron-right sm-item__chev\" aria-hidden=\"true\"></i>\n </button>\n\n <button\n type=\"button\"\n class=\"sm-item\"\n role=\"menuitem\"\n (click)=\"onSelect('going-out')\"\n >\n <span class=\"sm-item__well sm-item__well--orange\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-plane-departure\"></i>\n </span>\n <span class=\"sm-item__body\">\n <span class=\"sm-item__label\">{{ goingOutLabel() }}</span>\n <span class=\"sm-item__hint\">{{ goingOutHint() }}</span>\n </span>\n <i class=\"fa-solid fa-chevron-right sm-item__chev\" aria-hidden=\"true\"></i>\n </button>\n\n @if (showDocuments()) {\n <button\n type=\"button\"\n class=\"sm-item\"\n role=\"menuitem\"\n (click)=\"onSelect('documents')\"\n >\n <span class=\"sm-item__well sm-item__well--purple\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-folder\"></i>\n </span>\n <span class=\"sm-item__body\">\n <span class=\"sm-item__label\">Explorer</span>\n <span class=\"sm-item__hint\">Kho t\u00E0i li\u1EC7u c\u1EE7a b\u00E9</span>\n </span>\n <i class=\"fa-solid fa-chevron-right sm-item__chev\" aria-hidden=\"true\"></i>\n </button>\n }\n </nav>\n </div>\n\n <!-- \u2500\u2500 About \u2500\u2500 -->\n <div class=\"sm-section\">\n <div class=\"sm-section__label\">Th\u00F4ng tin v\u1EC1 \u1EE9ng d\u1EE5ng</div>\n <section class=\"sm-about\" aria-label=\"About\">\n <div class=\"sm-about__icon\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-circle-info\"></i>\n </div>\n <div class=\"sm-about__body\">\n <span class=\"sm-about__name\">{{ appName() }}</span>\n <span class=\"sm-about__version\">Phi\u00EAn b\u1EA3n {{ appVersion() }}</span>\n </div>\n </section>\n </div>\n\n <!-- \u2500\u2500 Danger \u2500\u2500 -->\n <div class=\"sm-section\">\n <div class=\"sm-section__label\">Danger Zone</div>\n <nav class=\"sm-list sm-list--card\" role=\"menu\">\n <button\n type=\"button\"\n class=\"sm-item sm-item--danger\"\n role=\"menuitem\"\n (click)=\"onSelect('delete')\"\n >\n <span class=\"sm-item__well sm-item__well--rose\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-trash\"></i>\n </span>\n <span class=\"sm-item__body\">\n <span class=\"sm-item__label\">Xo\u00E1 profile</span>\n <span class=\"sm-item__hint\">G\u1EE1 h\u1ED3 s\u01A1 b\u00E9 kh\u1ECFi thi\u1EBFt b\u1ECB n\u00E0y</span>\n </span>\n </button>\n </nav>\n </div>\n </div>\n</section>\n", styles: ["@charset \"UTF-8\";:host{--bg: #f2f3f7;--surface: #ffffff;--border: #e2e5ee;--primary: #5b6cff;--primary-soft: #eef0ff;--accent: #e0338c;--rose: #dc2626;--rose-soft: #fee2e2;--text: #0f172a;--muted: #64748b;--muted-2: #94a3b8;--cyan: #0891b2;--cyan-soft: #dff7fd;--green: #16a34a;--green-soft: #e4fbec;--pink: #e0338c;--pink-soft: #ffe8f3;--orange: #ea580c;--orange-soft: #fff0e2;--purple: #7c3aed;--purple-soft: #f2e9ff;--slate: #475569;--slate-soft: #f1f5f9;position:fixed;inset:0;z-index:210;background:var(--bg);display:flex;flex-direction:column;color:var(--text);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;animation:sm-slide-in .22s cubic-bezier(.22,1,.36,1)}@keyframes sm-slide-in{0%{transform:translate(8px);opacity:0}to{transform:translate(0);opacity:1}}.sm-root{display:flex;flex-direction:column;flex:1;min-height:0}.sm-header{flex-shrink:0;display:flex;align-items:center;gap:8px;padding:10px 14px;padding-top:calc(10px + env(safe-area-inset-top,0px));background:var(--surface);border-bottom:1px solid var(--border)}.sm-title{margin:0;font-size:17px;font-weight:700;color:var(--text)}.sm-spacer{flex:1}.sm-close{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:9px;border:none;background:var(--primary-soft);color:var(--primary);font-size:14px;cursor:pointer;-webkit-tap-highlight-color:transparent;transition:background .15s,color .15s}.sm-close:hover{filter:brightness(.97)}.sm-body{flex:1;min-height:0;overflow:auto;-webkit-overflow-scrolling:touch;padding:12px 0 calc(24px + env(safe-area-inset-bottom,0px))}.sm-profile{display:flex;align-items:center;gap:12px;width:calc(100% - 24px);margin:0 12px 14px;padding:10px 12px;border:1px solid var(--border);border-radius:16px;background:var(--surface);text-align:left;cursor:pointer;-webkit-tap-highlight-color:transparent;transition:background .15s}.sm-profile:hover{background:color-mix(in srgb,var(--primary-soft) 40%,var(--surface))}.sm-profile:active{background:var(--primary-soft)}.sm-profile__avatar{flex-shrink:0;width:40px;height:40px;border-radius:14px;background:var(--primary-soft);color:var(--primary);display:inline-flex;align-items:center;justify-content:center;font-size:15px;font-weight:800;letter-spacing:.02em}.sm-profile__body{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.sm-profile__name{font-size:15px;font-weight:700;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sm-profile__meta,.sm-profile__birth{font-size:12px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sm-profile__birth{color:var(--muted-2);font-size:11px}.sm-profile__edit{flex-shrink:0;display:inline-flex;align-items:center;gap:4px;font-size:11px;font-weight:700;color:var(--primary);background:var(--primary-soft);padding:4px 8px;border-radius:8px}.sm-profile__edit i{font-size:11px}.sm-section{margin-bottom:14px}.sm-section__label{padding:0 18px 6px;font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}.sm-tiles{margin:0 12px;display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px}.sm-tile{position:relative;display:flex;flex-direction:column;align-items:center;gap:6px;min-height:96px;padding:12px 8px 10px;border:1px solid var(--border);border-radius:14px;background:var(--surface);cursor:pointer;-webkit-tap-highlight-color:transparent;transition:background .15s}.sm-tile:hover{background:color-mix(in srgb,var(--primary-soft) 35%,var(--surface))}.sm-tile:active{background:var(--primary-soft)}.sm-tile__icon{width:38px;height:38px;border-radius:11px;display:inline-flex;align-items:center;justify-content:center;font-size:16px}.sm-tile__icon--slate{background:var(--slate-soft);color:var(--slate)}.sm-tile__icon--orange{background:var(--orange-soft);color:var(--orange)}.sm-tile__icon--pink{background:var(--pink-soft);color:var(--pink)}.sm-tile__label{font-size:11px;font-weight:700;color:var(--text);text-align:center;line-height:1.2}.sm-tile__hint{font-size:10px;color:var(--muted-2);text-align:center;line-height:1.2}.sm-tile__badge{position:absolute;top:8px;right:8px;min-width:18px;height:18px;padding:0 5px;border-radius:999px;background:var(--accent);color:#fff;font-size:10px;font-weight:700;display:inline-flex;align-items:center;justify-content:center}.sm-list{display:flex;flex-direction:column}.sm-list--card{margin:0 12px;border:1px solid var(--border);border-radius:14px;overflow:hidden;background:var(--surface)}.sm-item{display:flex;align-items:center;gap:12px;width:100%;padding:12px 14px;border:none;border-bottom:1px solid var(--border);background:transparent;text-align:left;cursor:pointer;-webkit-tap-highlight-color:transparent;transition:background .15s}.sm-item:last-child{border-bottom:none}.sm-item:hover{background:var(--primary-soft)}.sm-item:active{background:color-mix(in srgb,var(--primary-soft) 55%,var(--surface))}.sm-item__well{flex-shrink:0;width:36px;height:36px;border-radius:10px;display:inline-flex;align-items:center;justify-content:center;font-size:15px}.sm-item__well--cyan{background:var(--cyan-soft);color:var(--cyan)}.sm-item__well--green{background:var(--green-soft);color:var(--green)}.sm-item__well--pink{background:var(--pink-soft);color:var(--pink)}.sm-item__well--orange{background:var(--orange-soft);color:var(--orange)}.sm-item__well--purple{background:var(--purple-soft);color:var(--purple)}.sm-item__well--rose{background:var(--rose-soft);color:var(--rose)}.sm-item__body{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.sm-item__label{font-size:14px;font-weight:600;color:var(--text);line-height:1.25}.sm-item__hint{font-size:11px;color:var(--muted-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sm-item__chev{flex-shrink:0;font-size:13px;color:var(--muted-2)}.sm-item--danger:hover{background:var(--rose-soft)}.sm-item--danger:active{background:color-mix(in srgb,var(--rose-soft) 55%,var(--surface))}.sm-item--danger .sm-item__label{color:var(--rose)}.sm-about{display:flex;align-items:center;gap:12px;margin:0 12px;padding:12px 14px;border-radius:14px;background:var(--surface);border:1px solid var(--border)}.sm-about__icon{flex-shrink:0;width:36px;height:36px;border-radius:10px;background:var(--primary-soft);color:var(--primary);font-size:16px;display:flex;align-items:center;justify-content:center}.sm-about__body{display:flex;flex-direction:column;gap:2px;flex:1;min-width:0}.sm-about__name{font-size:14px;font-weight:700;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sm-about__version{font-size:11px;color:var(--muted)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
21753
|
+
}
|
|
21754
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SettingsMenuComponent, decorators: [{
|
|
21755
|
+
type: Component,
|
|
21756
|
+
args: [{ selector: 'app-settings-menu', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section class=\"sm-root\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Menu\">\n <header class=\"sm-header\">\n <h1 class=\"sm-title\">{{ appName() }} d\u00E0nh cho <em>{{ babyName() }}</em></h1>\n <span class=\"sm-spacer\"></span>\n <button type=\"button\" class=\"sm-close\" (click)=\"close.emit()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"fa-solid fa-xmark\" aria-hidden=\"true\"></i>\n </button>\n </header>\n\n <div class=\"sm-body\">\n <!-- \u2500\u2500 Profile card \u2500\u2500 -->\n <button\n type=\"button\"\n class=\"sm-profile\"\n (click)=\"onSelect('edit')\"\n aria-label=\"S\u1EEDa profile b\u00E9\"\n >\n <span class=\"sm-profile__avatar\" aria-hidden=\"true\">{{ initials() }}</span>\n <span class=\"sm-profile__body\">\n <span class=\"sm-profile__name\">{{ babyName() || 'Ch\u01B0a c\u00F3 t\u00EAn' }}</span>\n @if (profileMeta()) {\n <span class=\"sm-profile__meta\">{{ profileMeta() }}</span>\n }\n @if (birthDateLabel()) {\n <span class=\"sm-profile__birth\">Sinh {{ birthDateLabel() }}</span>\n }\n </span>\n <span class=\"sm-profile__edit\">\n <i class=\"fa-solid fa-pencil\" aria-hidden=\"true\"></i>\n S\u1EEDa\n </span>\n </button>\n\n <!-- \u2500\u2500 Settings tiles \u2500\u2500 -->\n <div class=\"sm-section\">\n <div class=\"sm-section__label\">C\u00E0i \u0111\u1EB7t</div>\n <div class=\"sm-tiles\" role=\"menu\">\n <button\n type=\"button\"\n class=\"sm-tile\"\n role=\"menuitem\"\n (click)=\"onSelect('settings')\"\n >\n <span class=\"sm-tile__icon sm-tile__icon--slate\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-gear\"></i>\n </span>\n <span class=\"sm-tile__label\">C\u00E0i \u0111\u1EB7t chung</span>\n <span class=\"sm-tile__hint\">C\u00E0i \u0111\u1EB7t \u1EE9ng d\u1EE5ng</span>\n </button>\n\n <button\n type=\"button\"\n class=\"sm-tile\"\n role=\"menuitem\"\n (click)=\"onSelect('going-out-list')\"\n >\n <span class=\"sm-tile__icon sm-tile__icon--orange\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-clipboard-check\"></i>\n </span>\n <span class=\"sm-tile__label\">\u0110i ch\u01A1i</span>\n <span class=\"sm-tile__hint\">Qu\u1EA3n l\u00FD \u0111\u1ED3 \u0111\u1EA1c</span>\n </button>\n\n <button\n type=\"button\"\n class=\"sm-tile sm-tile--badge\"\n role=\"menuitem\"\n (click)=\"onSelect('preparing-purchase')\"\n >\n <span class=\"sm-tile__icon sm-tile__icon--green\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-cart-shopping\"></i>\n </span>\n <span class=\"sm-tile__label\">Mua s\u1EAFm</span>\n <span class=\"sm-tile__hint\">Checkin \u0111\u1ED3 c\u1EA7n mua</span>\n @if (preparingPendingCount() > 0) {\n <span class=\"sm-tile__badge\">{{ preparingPendingCount() }}</span>\n }\n </button>\n\n <button\n type=\"button\"\n class=\"sm-tile sm-tile--badge\"\n role=\"menuitem\"\n (click)=\"onSelect('notifications')\"\n >\n <span class=\"sm-tile__icon sm-tile__icon--pink\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-bell\"></i>\n </span>\n <span class=\"sm-tile__label\">Th\u00F4ng b\u00E1o</span>\n <span class=\"sm-tile__hint\">M\u1EDBi & \u0111\u00E3 \u0111\u1ECDc</span>\n @if (unreadCount() > 0) {\n <span class=\"sm-tile__badge\">{{ unreadCount() }}</span>\n }\n </button>\n </div>\n </div>\n\n <!-- \u2500\u2500 Features list \u2500\u2500 -->\n <div class=\"sm-section\">\n <div class=\"sm-section__label\">T\u00EDnh n\u0103ng</div>\n <nav class=\"sm-list sm-list--card\" role=\"menu\">\n <button\n type=\"button\"\n class=\"sm-item\"\n role=\"menuitem\"\n (click)=\"onSelect('daily-summary')\"\n >\n <span class=\"sm-item__well sm-item__well--cyan\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-chart-pie\"></i>\n </span>\n <span class=\"sm-item__body\">\n <span class=\"sm-item__label\">T\u1ED5ng qu\u00E1t v\u1EC1 c\u00E1c ng\u00E0y tr\u01B0\u1EDBc</span>\n <span class=\"sm-item__hint\">Th\u00F4ng tin v\u1EC1 c\u1EEF b\u00FA v\u00E0 c\u00E1c ho\u1EA1t \u0111\u1ED9ng</span>\n </span>\n <i class=\"fa-solid fa-chevron-right sm-item__chev\" aria-hidden=\"true\"></i>\n </button>\n\n <button type=\"button\" class=\"sm-item\" role=\"menuitem\" (click)=\"onSelect('weight')\">\n <span class=\"sm-item__well sm-item__well--green\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-chart-line\"></i>\n </span>\n <span class=\"sm-item__body\">\n <span class=\"sm-item__label\">Th\u1EC3 tr\u1EA1ng</span>\n <span class=\"sm-item__hint\">C\u00E2n n\u1EB7ng, chi\u1EC1u cao, t\u0103ng tr\u01B0\u1EDFng c\u1EE7a b\u00E9</span>\n </span>\n <i class=\"fa-solid fa-chevron-right sm-item__chev\" aria-hidden=\"true\"></i>\n </button>\n\n <button type=\"button\" class=\"sm-item\" role=\"menuitem\" (click)=\"onSelect('mom')\">\n <span class=\"sm-item__well sm-item__well--pink\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-person-pregnant\"></i>\n </span>\n <span class=\"sm-item__body\">\n <span class=\"sm-item__label\">G\u00F3c c\u1EE7a M\u1EB9</span>\n <span class=\"sm-item__hint\">S\u1EE9c kho\u1EBB & dinh d\u01B0\u1EE1ng</span>\n </span>\n <i class=\"fa-solid fa-chevron-right sm-item__chev\" aria-hidden=\"true\"></i>\n </button>\n\n <button\n type=\"button\"\n class=\"sm-item\"\n role=\"menuitem\"\n (click)=\"onSelect('going-out')\"\n >\n <span class=\"sm-item__well sm-item__well--orange\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-plane-departure\"></i>\n </span>\n <span class=\"sm-item__body\">\n <span class=\"sm-item__label\">{{ goingOutLabel() }}</span>\n <span class=\"sm-item__hint\">{{ goingOutHint() }}</span>\n </span>\n <i class=\"fa-solid fa-chevron-right sm-item__chev\" aria-hidden=\"true\"></i>\n </button>\n\n @if (showDocuments()) {\n <button\n type=\"button\"\n class=\"sm-item\"\n role=\"menuitem\"\n (click)=\"onSelect('documents')\"\n >\n <span class=\"sm-item__well sm-item__well--purple\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-folder\"></i>\n </span>\n <span class=\"sm-item__body\">\n <span class=\"sm-item__label\">Explorer</span>\n <span class=\"sm-item__hint\">Kho t\u00E0i li\u1EC7u c\u1EE7a b\u00E9</span>\n </span>\n <i class=\"fa-solid fa-chevron-right sm-item__chev\" aria-hidden=\"true\"></i>\n </button>\n }\n </nav>\n </div>\n\n <!-- \u2500\u2500 About \u2500\u2500 -->\n <div class=\"sm-section\">\n <div class=\"sm-section__label\">Th\u00F4ng tin v\u1EC1 \u1EE9ng d\u1EE5ng</div>\n <section class=\"sm-about\" aria-label=\"About\">\n <div class=\"sm-about__icon\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-circle-info\"></i>\n </div>\n <div class=\"sm-about__body\">\n <span class=\"sm-about__name\">{{ appName() }}</span>\n <span class=\"sm-about__version\">Phi\u00EAn b\u1EA3n {{ appVersion() }}</span>\n </div>\n </section>\n </div>\n\n <!-- \u2500\u2500 Danger \u2500\u2500 -->\n <div class=\"sm-section\">\n <div class=\"sm-section__label\">Danger Zone</div>\n <nav class=\"sm-list sm-list--card\" role=\"menu\">\n <button\n type=\"button\"\n class=\"sm-item sm-item--danger\"\n role=\"menuitem\"\n (click)=\"onSelect('delete')\"\n >\n <span class=\"sm-item__well sm-item__well--rose\" aria-hidden=\"true\">\n <i class=\"fa-solid fa-trash\"></i>\n </span>\n <span class=\"sm-item__body\">\n <span class=\"sm-item__label\">Xo\u00E1 profile</span>\n <span class=\"sm-item__hint\">G\u1EE1 h\u1ED3 s\u01A1 b\u00E9 kh\u1ECFi thi\u1EBFt b\u1ECB n\u00E0y</span>\n </span>\n </button>\n </nav>\n </div>\n </div>\n</section>\n", styles: ["@charset \"UTF-8\";:host{--bg: #f2f3f7;--surface: #ffffff;--border: #e2e5ee;--primary: #5b6cff;--primary-soft: #eef0ff;--accent: #e0338c;--rose: #dc2626;--rose-soft: #fee2e2;--text: #0f172a;--muted: #64748b;--muted-2: #94a3b8;--cyan: #0891b2;--cyan-soft: #dff7fd;--green: #16a34a;--green-soft: #e4fbec;--pink: #e0338c;--pink-soft: #ffe8f3;--orange: #ea580c;--orange-soft: #fff0e2;--purple: #7c3aed;--purple-soft: #f2e9ff;--slate: #475569;--slate-soft: #f1f5f9;position:fixed;inset:0;z-index:210;background:var(--bg);display:flex;flex-direction:column;color:var(--text);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;animation:sm-slide-in .22s cubic-bezier(.22,1,.36,1)}@keyframes sm-slide-in{0%{transform:translate(8px);opacity:0}to{transform:translate(0);opacity:1}}.sm-root{display:flex;flex-direction:column;flex:1;min-height:0}.sm-header{flex-shrink:0;display:flex;align-items:center;gap:8px;padding:10px 14px;padding-top:calc(10px + env(safe-area-inset-top,0px));background:var(--surface);border-bottom:1px solid var(--border)}.sm-title{margin:0;font-size:17px;font-weight:700;color:var(--text)}.sm-spacer{flex:1}.sm-close{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:9px;border:none;background:var(--primary-soft);color:var(--primary);font-size:14px;cursor:pointer;-webkit-tap-highlight-color:transparent;transition:background .15s,color .15s}.sm-close:hover{filter:brightness(.97)}.sm-body{flex:1;min-height:0;overflow:auto;-webkit-overflow-scrolling:touch;padding:12px 0 calc(24px + env(safe-area-inset-bottom,0px))}.sm-profile{display:flex;align-items:center;gap:12px;width:calc(100% - 24px);margin:0 12px 14px;padding:10px 12px;border:1px solid var(--border);border-radius:16px;background:var(--surface);text-align:left;cursor:pointer;-webkit-tap-highlight-color:transparent;transition:background .15s}.sm-profile:hover{background:color-mix(in srgb,var(--primary-soft) 40%,var(--surface))}.sm-profile:active{background:var(--primary-soft)}.sm-profile__avatar{flex-shrink:0;width:40px;height:40px;border-radius:14px;background:var(--primary-soft);color:var(--primary);display:inline-flex;align-items:center;justify-content:center;font-size:15px;font-weight:800;letter-spacing:.02em}.sm-profile__body{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.sm-profile__name{font-size:15px;font-weight:700;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sm-profile__meta,.sm-profile__birth{font-size:12px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sm-profile__birth{color:var(--muted-2);font-size:11px}.sm-profile__edit{flex-shrink:0;display:inline-flex;align-items:center;gap:4px;font-size:11px;font-weight:700;color:var(--primary);background:var(--primary-soft);padding:4px 8px;border-radius:8px}.sm-profile__edit i{font-size:11px}.sm-section{margin-bottom:14px}.sm-section__label{padding:0 18px 6px;font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}.sm-tiles{margin:0 12px;display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px}.sm-tile{position:relative;display:flex;flex-direction:column;align-items:center;gap:6px;min-height:96px;padding:12px 8px 10px;border:1px solid var(--border);border-radius:14px;background:var(--surface);cursor:pointer;-webkit-tap-highlight-color:transparent;transition:background .15s}.sm-tile:hover{background:color-mix(in srgb,var(--primary-soft) 35%,var(--surface))}.sm-tile:active{background:var(--primary-soft)}.sm-tile__icon{width:38px;height:38px;border-radius:11px;display:inline-flex;align-items:center;justify-content:center;font-size:16px}.sm-tile__icon--slate{background:var(--slate-soft);color:var(--slate)}.sm-tile__icon--orange{background:var(--orange-soft);color:var(--orange)}.sm-tile__icon--pink{background:var(--pink-soft);color:var(--pink)}.sm-tile__label{font-size:11px;font-weight:700;color:var(--text);text-align:center;line-height:1.2}.sm-tile__hint{font-size:10px;color:var(--muted-2);text-align:center;line-height:1.2}.sm-tile__badge{position:absolute;top:8px;right:8px;min-width:18px;height:18px;padding:0 5px;border-radius:999px;background:var(--accent);color:#fff;font-size:10px;font-weight:700;display:inline-flex;align-items:center;justify-content:center}.sm-list{display:flex;flex-direction:column}.sm-list--card{margin:0 12px;border:1px solid var(--border);border-radius:14px;overflow:hidden;background:var(--surface)}.sm-item{display:flex;align-items:center;gap:12px;width:100%;padding:12px 14px;border:none;border-bottom:1px solid var(--border);background:transparent;text-align:left;cursor:pointer;-webkit-tap-highlight-color:transparent;transition:background .15s}.sm-item:last-child{border-bottom:none}.sm-item:hover{background:var(--primary-soft)}.sm-item:active{background:color-mix(in srgb,var(--primary-soft) 55%,var(--surface))}.sm-item__well{flex-shrink:0;width:36px;height:36px;border-radius:10px;display:inline-flex;align-items:center;justify-content:center;font-size:15px}.sm-item__well--cyan{background:var(--cyan-soft);color:var(--cyan)}.sm-item__well--green{background:var(--green-soft);color:var(--green)}.sm-item__well--pink{background:var(--pink-soft);color:var(--pink)}.sm-item__well--orange{background:var(--orange-soft);color:var(--orange)}.sm-item__well--purple{background:var(--purple-soft);color:var(--purple)}.sm-item__well--rose{background:var(--rose-soft);color:var(--rose)}.sm-item__body{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.sm-item__label{font-size:14px;font-weight:600;color:var(--text);line-height:1.25}.sm-item__hint{font-size:11px;color:var(--muted-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sm-item__chev{flex-shrink:0;font-size:13px;color:var(--muted-2)}.sm-item--danger:hover{background:var(--rose-soft)}.sm-item--danger:active{background:color-mix(in srgb,var(--rose-soft) 55%,var(--surface))}.sm-item--danger .sm-item__label{color:var(--rose)}.sm-about{display:flex;align-items:center;gap:12px;margin:0 12px;padding:12px 14px;border-radius:14px;background:var(--surface);border:1px solid var(--border)}.sm-about__icon{flex-shrink:0;width:36px;height:36px;border-radius:10px;background:var(--primary-soft);color:var(--primary);font-size:16px;display:flex;align-items:center;justify-content:center}.sm-about__body{display:flex;flex-direction:column;gap:2px;flex:1;min-width:0}.sm-about__name{font-size:14px;font-weight:700;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.sm-about__version{font-size:11px;color:var(--muted)}\n"] }]
|
|
21757
|
+
}], propDecorators: { appName: [{ type: i0.Input, args: [{ isSignal: true, alias: "appName", required: false }] }], appVersion: [{ type: i0.Input, args: [{ isSignal: true, alias: "appVersion", required: false }] }], showDocuments: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDocuments", required: false }] }], unreadCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "unreadCount", required: false }] }], preparingPendingCount: [{ type: i0.Input, args: [{ isSignal: true, alias: "preparingPendingCount", required: false }] }], babyName: [{ type: i0.Input, args: [{ isSignal: true, alias: "babyName", required: false }] }], ageLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ageLabel", required: false }] }], weightLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "weightLabel", required: false }] }], birthDateLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "birthDateLabel", required: false }] }], close: [{ type: i0.Output, args: ["close"] }], select: [{ type: i0.Output, args: ["select"] }] } });
|
|
21758
|
+
|
|
21512
21759
|
/**
|
|
21513
21760
|
* Dialog sửa/thêm môn — dùng chung cho trang checklist (có note + trạng thái)
|
|
21514
21761
|
* và trang cài đặt danh sách (chỉ tiêu đề + mô tả). Khi `canEditTitleDesc`
|
|
@@ -21520,6 +21767,7 @@ class GoingOutEditDialogComponent {
|
|
|
21520
21767
|
this.heading = 'Sửa món';
|
|
21521
21768
|
this.showStatus = false;
|
|
21522
21769
|
this.showNote = false;
|
|
21770
|
+
this.showNotNeeded = false;
|
|
21523
21771
|
this.canEditTitleDesc = true;
|
|
21524
21772
|
/** Cho phép trạng thái "1 phần" trong bộ chọn (mặc định true). */
|
|
21525
21773
|
this.allowPartial = true;
|
|
@@ -21527,16 +21775,21 @@ class GoingOutEditDialogComponent {
|
|
|
21527
21775
|
this.initialDesc = '';
|
|
21528
21776
|
this.initialNote = '';
|
|
21529
21777
|
this.initialStatus = 'none';
|
|
21778
|
+
this.initialNotNeeded = false;
|
|
21779
|
+
/** Cho phép xoá món khỏi checklist chuyến (chỉ checklist). */
|
|
21780
|
+
this.showRemoveFromTrip = false;
|
|
21530
21781
|
this.cancel = new EventEmitter();
|
|
21531
21782
|
this.save = new EventEmitter();
|
|
21783
|
+
this.removeFromTrip = new EventEmitter();
|
|
21532
21784
|
this.draftTitle = signal('', ...(ngDevMode ? [{ debugName: "draftTitle" }] : /* istanbul ignore next */ []));
|
|
21533
21785
|
this.draftDesc = signal('', ...(ngDevMode ? [{ debugName: "draftDesc" }] : /* istanbul ignore next */ []));
|
|
21534
21786
|
this.draftNote = signal('', ...(ngDevMode ? [{ debugName: "draftNote" }] : /* istanbul ignore next */ []));
|
|
21535
21787
|
this.draftStatus = signal('none', ...(ngDevMode ? [{ debugName: "draftStatus" }] : /* istanbul ignore next */ []));
|
|
21788
|
+
this.draftNotNeeded = signal(false, ...(ngDevMode ? [{ debugName: "draftNotNeeded" }] : /* istanbul ignore next */ []));
|
|
21536
21789
|
this.statuses = [
|
|
21537
|
-
{ id: 'none', icon: 'pi pi-circle', label: 'Chưa
|
|
21538
|
-
{ id: 'partial', icon: 'pi pi-circle-fill', label: '
|
|
21539
|
-
{ id: 'done', icon: 'pi pi-check-circle', label: '
|
|
21790
|
+
{ id: 'none', icon: 'pi pi-circle', label: 'Chưa' },
|
|
21791
|
+
{ id: 'partial', icon: 'pi pi-circle-fill', label: 'Đang làm' },
|
|
21792
|
+
{ id: 'done', icon: 'pi pi-check-circle', label: 'Xong' },
|
|
21540
21793
|
];
|
|
21541
21794
|
this.visibleStatuses = computed(() => this.allowPartial ? this.statuses : this.statuses.filter((s) => s.id !== 'partial'), ...(ngDevMode ? [{ debugName: "visibleStatuses" }] : /* istanbul ignore next */ []));
|
|
21542
21795
|
}
|
|
@@ -21545,6 +21798,7 @@ class GoingOutEditDialogComponent {
|
|
|
21545
21798
|
this.draftDesc.set(this.initialDesc);
|
|
21546
21799
|
this.draftNote.set(this.initialNote);
|
|
21547
21800
|
this.draftStatus.set(this.initialStatus);
|
|
21801
|
+
this.draftNotNeeded.set(this.initialNotNeeded);
|
|
21548
21802
|
}
|
|
21549
21803
|
onTitle(v) {
|
|
21550
21804
|
this.draftTitle.set(v);
|
|
@@ -21556,24 +21810,33 @@ class GoingOutEditDialogComponent {
|
|
|
21556
21810
|
this.draftNote.set(v);
|
|
21557
21811
|
}
|
|
21558
21812
|
pickStatus(s) {
|
|
21813
|
+
this.draftNotNeeded.set(false);
|
|
21559
21814
|
this.draftStatus.set(s);
|
|
21560
21815
|
}
|
|
21816
|
+
setNotNeeded(checked) {
|
|
21817
|
+
this.draftNotNeeded.set(checked);
|
|
21818
|
+
if (checked) {
|
|
21819
|
+
this.draftStatus.set('none');
|
|
21820
|
+
}
|
|
21821
|
+
}
|
|
21561
21822
|
emitSave() {
|
|
21562
21823
|
if (this.mode === 'add' && !this.draftTitle().trim())
|
|
21563
21824
|
return;
|
|
21825
|
+
const notNeeded = this.draftNotNeeded();
|
|
21564
21826
|
this.save.emit({
|
|
21565
21827
|
title: this.draftTitle(),
|
|
21566
21828
|
desc: this.draftDesc(),
|
|
21567
21829
|
note: this.draftNote(),
|
|
21568
|
-
status: this.draftStatus(),
|
|
21830
|
+
status: notNeeded ? 'none' : this.draftStatus(),
|
|
21831
|
+
notNeeded,
|
|
21569
21832
|
});
|
|
21570
21833
|
}
|
|
21571
21834
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: GoingOutEditDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
21572
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: GoingOutEditDialogComponent, isStandalone: true, selector: "app-going-out-edit-dialog", inputs: { mode: "mode", heading: "heading", showStatus: "showStatus", showNote: "showNote", canEditTitleDesc: "canEditTitleDesc", allowPartial: "allowPartial", initialTitle: "initialTitle", initialDesc: "initialDesc", initialNote: "initialNote", initialStatus: "initialStatus" }, outputs: { cancel: "cancel", save: "save" }, ngImport: i0, template: "<div class=\"go-overlay\" (click)=\"cancel.emit()\" role=\"presentation\">\n <div\n class=\"go-sheet\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"heading\"\n (click)=\"$event.stopPropagation()\"\n >\n <header class=\"go-sheet__head\">\n <h2 class=\"go-sheet__title\">{{ heading }}</h2>\n <button type=\"button\" class=\"go-icon-btn\" (click)=\"cancel.emit()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\n </button>\n </header>\n\n <!-- Ti\u00EAu \u0111\u1EC1 -->\n <label class=\"go-field\">\n <span class=\"go-field__label\">Ti\u00EAu \u0111\u1EC1</span>\n @if (canEditTitleDesc) {\n <input\n class=\"go-input\"\n type=\"text\"\n [value]=\"draftTitle()\"\n (input)=\"onTitle($any($event.target).value)\"\n placeholder=\"VD: T\u00E3 gi\u1EA5y\"\n />\n } @else {\n <p class=\"go-readonly\">{{ draftTitle() }}</p>\n }\n </label>\n\n <!-- M\u00F4 t\u1EA3 -->\n @if (canEditTitleDesc || draftDesc()) {\n <label class=\"go-field\">\n <span class=\"go-field__label\">M\u00F4 t\u1EA3</span>\n @if (canEditTitleDesc) {\n <textarea\n class=\"go-input go-input--area\"\n rows=\"2\"\n [value]=\"draftDesc()\"\n (input)=\"onDesc($any($event.target).value)\"\n placeholder=\"S\u1ED1 l\u01B0\u1EE3ng, l\u01B0u \u00FD khi mang theo\u2026\"\n ></textarea>\n } @else {\n <p class=\"go-readonly go-readonly--muted\">{{ draftDesc() }}</p>\n }\n </label>\n }\n\n <!-- Ghi ch\u00FA (ch\u1EC9 trang checklist) -->\n @if (showNote) {\n <label class=\"go-field\">\n <span class=\"go-field__label\">Ghi ch\u00FA chuy\u1EBFn \u0111i</span>\n <textarea\n class=\"go-input go-input--area\"\n rows=\"2\"\n [value]=\"draftNote()\"\n (input)=\"onNote($any($event.target).value)\"\n placeholder=\"Ghi ch\u00FA ri\u00EAng cho chuy\u1EBFn n\u00E0y\u2026\"\n ></textarea>\n </label>\n }\n\n <!-- Tr\u1EA1ng th\u00E1i (ch\u1EC9 trang checklist) -->\n @if (showStatus) {\n <div class=\"go-field\">\n <span class=\"go-field__label\">Tr\u1EA1ng th\u00E1i</span>\n <div
|
|
21835
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: GoingOutEditDialogComponent, isStandalone: true, selector: "app-going-out-edit-dialog", inputs: { mode: "mode", heading: "heading", showStatus: "showStatus", showNote: "showNote", showNotNeeded: "showNotNeeded", canEditTitleDesc: "canEditTitleDesc", allowPartial: "allowPartial", initialTitle: "initialTitle", initialDesc: "initialDesc", initialNote: "initialNote", initialStatus: "initialStatus", initialNotNeeded: "initialNotNeeded", showRemoveFromTrip: "showRemoveFromTrip" }, outputs: { cancel: "cancel", save: "save", removeFromTrip: "removeFromTrip" }, ngImport: i0, template: "<div class=\"go-overlay\" (click)=\"cancel.emit()\" role=\"presentation\">\n <div\n class=\"go-sheet\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"heading\"\n (click)=\"$event.stopPropagation()\"\n >\n <header class=\"go-sheet__head\">\n <h2 class=\"go-sheet__title\">{{ heading }}</h2>\n <button type=\"button\" class=\"go-icon-btn\" (click)=\"cancel.emit()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\n </button>\n </header>\n\n <!-- Ti\u00EAu \u0111\u1EC1 -->\n <label class=\"go-field\">\n <span class=\"go-field__label\">Ti\u00EAu \u0111\u1EC1</span>\n @if (canEditTitleDesc) {\n <input\n class=\"go-input\"\n type=\"text\"\n [value]=\"draftTitle()\"\n (input)=\"onTitle($any($event.target).value)\"\n placeholder=\"VD: T\u00E3 gi\u1EA5y\"\n />\n } @else {\n <p class=\"go-readonly\">{{ draftTitle() }}</p>\n }\n </label>\n\n <!-- M\u00F4 t\u1EA3 -->\n @if (canEditTitleDesc || draftDesc()) {\n <label class=\"go-field\">\n <span class=\"go-field__label\">M\u00F4 t\u1EA3</span>\n @if (canEditTitleDesc) {\n <textarea\n class=\"go-input go-input--area\"\n rows=\"2\"\n [value]=\"draftDesc()\"\n (input)=\"onDesc($any($event.target).value)\"\n placeholder=\"S\u1ED1 l\u01B0\u1EE3ng, l\u01B0u \u00FD khi mang theo\u2026\"\n ></textarea>\n } @else {\n <p class=\"go-readonly go-readonly--muted\">{{ draftDesc() }}</p>\n }\n </label>\n }\n\n <!-- Ghi ch\u00FA (ch\u1EC9 trang checklist) -->\n @if (showNote) {\n <label class=\"go-field\">\n <span class=\"go-field__label\">Ghi ch\u00FA chuy\u1EBFn \u0111i</span>\n <textarea\n class=\"go-input go-input--area\"\n rows=\"2\"\n [value]=\"draftNote()\"\n (input)=\"onNote($any($event.target).value)\"\n placeholder=\"Ghi ch\u00FA ri\u00EAng cho chuy\u1EBFn n\u00E0y\u2026\"\n ></textarea>\n </label>\n }\n\n <!-- Tr\u1EA1ng th\u00E1i (ch\u1EC9 trang checklist) -->\n @if (showStatus) {\n <div class=\"go-field\">\n <span class=\"go-field__label\">Tr\u1EA1ng th\u00E1i</span>\n <div\n class=\"go-status-lg\"\n role=\"radiogroup\"\n aria-label=\"Tr\u1EA1ng th\u00E1i\"\n [class.go-status-lg--disabled]=\"draftNotNeeded()\"\n >\n @for (s of visibleStatuses(); track s.id) {\n <button\n type=\"button\"\n class=\"go-status-lg__seg\"\n [class.go-status-lg__seg--active]=\"draftStatus() === s.id && !draftNotNeeded()\"\n (click)=\"pickStatus(s.id)\"\n [attr.aria-pressed]=\"draftStatus() === s.id && !draftNotNeeded()\"\n [disabled]=\"draftNotNeeded()\"\n >\n <i [class]=\"s.icon\" aria-hidden=\"true\"></i>\n <span>{{ s.label }}</span>\n </button>\n }\n </div>\n </div>\n }\n\n @if (showNotNeeded) {\n <label class=\"go-toggle-row\">\n <span class=\"go-toggle-row__text\">Kh\u00F4ng c\u1EA7n mang l\u1EA7n n\u00E0y</span>\n <input\n type=\"checkbox\"\n role=\"switch\"\n [checked]=\"draftNotNeeded()\"\n (change)=\"setNotNeeded($any($event.target).checked)\"\n />\n </label>\n }\n\n <footer class=\"go-sheet__foot\">\n @if (showRemoveFromTrip) {\n <button type=\"button\" class=\"go-btn go-btn--danger\" (click)=\"removeFromTrip.emit()\">\n B\u1ECF kh\u1ECFi chuy\u1EBFn\n </button>\n }\n <span class=\"go-sheet__foot-spacer\"></span>\n <button type=\"button\" class=\"go-btn go-btn--ghost\" (click)=\"cancel.emit()\">Hu\u1EF7</button>\n <button type=\"button\" class=\"go-btn go-btn--primary\" (click)=\"emitSave()\">\n {{ mode === 'add' ? 'Th\u00EAm' : 'L\u01B0u' }}\n </button>\n </footer>\n </div>\n</div>\n", styles: [":host{--bg: #f6f7fb;--surface: #ffffff;--border: #e6e8ef;--primary: #5b6cff;--primary-soft: #eef0ff;--text: #0f172a;--muted: #475569;--muted-2: #94a3b8;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;color:var(--text)}.go-overlay{position:fixed;inset:0;z-index:420;background:#0f172a73;display:flex;align-items:center;justify-content:center;padding:16px;padding-top:calc(16px + env(safe-area-inset-top,0px));padding-bottom:calc(16px + env(safe-area-inset-bottom,0px));animation:go-fade .18s ease-out}@keyframes go-fade{0%{opacity:0}to{opacity:1}}.go-sheet{width:100%;max-width:460px;max-height:min(85vh,640px);overflow:auto;background:var(--surface);border-radius:18px;padding:18px 16px calc(16px + env(safe-area-inset-bottom,0px));display:flex;flex-direction:column;gap:14px;animation:go-sheet-in .22s cubic-bezier(.22,1,.36,1)}@keyframes go-sheet-in{0%{transform:translateY(12px);opacity:0}to{transform:translateY(0);opacity:1}}.go-sheet__head{display:flex;align-items:center;gap:8px}.go-sheet__title{margin:0;font-size:16px;font-weight:700;flex:1}.go-icon-btn{width:32px;height:32px;border:none;background:transparent;border-radius:9px;color:var(--muted);font-size:15px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;-webkit-tap-highlight-color:transparent}.go-icon-btn:hover{background:var(--primary-soft);color:var(--primary)}.go-field{display:flex;flex-direction:column;gap:6px}.go-field__label{font-size:12px;font-weight:600;color:var(--muted)}.go-input{width:100%;padding:10px 12px;border:1px solid var(--border);border-radius:10px;background:var(--bg);color:var(--text);font-size:15px;font-family:inherit;outline:none;transition:border-color .15s}.go-input:focus{border-color:var(--primary)}.go-input--area{resize:vertical;min-height:56px}.go-readonly{margin:0;font-size:15px;font-weight:600;color:var(--text)}.go-readonly--muted{font-weight:400;font-size:14px;color:var(--muted)}.go-status-lg{display:flex;gap:6px;border:1px solid var(--border);border-radius:12px;padding:4px;background:var(--bg)}.go-status-lg--disabled{opacity:.5}.go-status-lg__seg{flex:1;display:flex;flex-direction:column;align-items:center;gap:4px;padding:8px 4px;border:none;background:transparent;border-radius:9px;color:var(--muted);font-size:12px;font-weight:600;cursor:pointer;-webkit-tap-highlight-color:transparent}.go-status-lg__seg i{font-size:16px}.go-status-lg__seg--active{background:var(--primary);color:#fff}.go-toggle-row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 12px;border:1px solid var(--border);border-radius:12px;background:var(--bg);cursor:pointer}.go-toggle-row__text{font-size:14px;font-weight:600;color:var(--text)}.go-sheet__foot{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin-top:2px}.go-sheet__foot-spacer{flex:1}.go-btn{border:none;border-radius:10px;padding:9px 16px;font-size:14px;font-weight:600;cursor:pointer;-webkit-tap-highlight-color:transparent}.go-btn--ghost{background:transparent;color:var(--muted)}.go-btn--primary{background:var(--primary);color:#fff}.go-btn--danger{background:transparent;color:#dc2626;padding-left:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
21573
21836
|
}
|
|
21574
21837
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: GoingOutEditDialogComponent, decorators: [{
|
|
21575
21838
|
type: Component,
|
|
21576
|
-
args: [{ selector: 'app-going-out-edit-dialog', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"go-overlay\" (click)=\"cancel.emit()\" role=\"presentation\">\n <div\n class=\"go-sheet\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"heading\"\n (click)=\"$event.stopPropagation()\"\n >\n <header class=\"go-sheet__head\">\n <h2 class=\"go-sheet__title\">{{ heading }}</h2>\n <button type=\"button\" class=\"go-icon-btn\" (click)=\"cancel.emit()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\n </button>\n </header>\n\n <!-- Ti\u00EAu \u0111\u1EC1 -->\n <label class=\"go-field\">\n <span class=\"go-field__label\">Ti\u00EAu \u0111\u1EC1</span>\n @if (canEditTitleDesc) {\n <input\n class=\"go-input\"\n type=\"text\"\n [value]=\"draftTitle()\"\n (input)=\"onTitle($any($event.target).value)\"\n placeholder=\"VD: T\u00E3 gi\u1EA5y\"\n />\n } @else {\n <p class=\"go-readonly\">{{ draftTitle() }}</p>\n }\n </label>\n\n <!-- M\u00F4 t\u1EA3 -->\n @if (canEditTitleDesc || draftDesc()) {\n <label class=\"go-field\">\n <span class=\"go-field__label\">M\u00F4 t\u1EA3</span>\n @if (canEditTitleDesc) {\n <textarea\n class=\"go-input go-input--area\"\n rows=\"2\"\n [value]=\"draftDesc()\"\n (input)=\"onDesc($any($event.target).value)\"\n placeholder=\"S\u1ED1 l\u01B0\u1EE3ng, l\u01B0u \u00FD khi mang theo\u2026\"\n ></textarea>\n } @else {\n <p class=\"go-readonly go-readonly--muted\">{{ draftDesc() }}</p>\n }\n </label>\n }\n\n <!-- Ghi ch\u00FA (ch\u1EC9 trang checklist) -->\n @if (showNote) {\n <label class=\"go-field\">\n <span class=\"go-field__label\">Ghi ch\u00FA chuy\u1EBFn \u0111i</span>\n <textarea\n class=\"go-input go-input--area\"\n rows=\"2\"\n [value]=\"draftNote()\"\n (input)=\"onNote($any($event.target).value)\"\n placeholder=\"Ghi ch\u00FA ri\u00EAng cho chuy\u1EBFn n\u00E0y\u2026\"\n ></textarea>\n </label>\n }\n\n <!-- Tr\u1EA1ng th\u00E1i (ch\u1EC9 trang checklist) -->\n @if (showStatus) {\n <div class=\"go-field\">\n <span class=\"go-field__label\">Tr\u1EA1ng th\u00E1i</span>\n <div
|
|
21839
|
+
args: [{ selector: 'app-going-out-edit-dialog', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"go-overlay\" (click)=\"cancel.emit()\" role=\"presentation\">\n <div\n class=\"go-sheet\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"heading\"\n (click)=\"$event.stopPropagation()\"\n >\n <header class=\"go-sheet__head\">\n <h2 class=\"go-sheet__title\">{{ heading }}</h2>\n <button type=\"button\" class=\"go-icon-btn\" (click)=\"cancel.emit()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\n </button>\n </header>\n\n <!-- Ti\u00EAu \u0111\u1EC1 -->\n <label class=\"go-field\">\n <span class=\"go-field__label\">Ti\u00EAu \u0111\u1EC1</span>\n @if (canEditTitleDesc) {\n <input\n class=\"go-input\"\n type=\"text\"\n [value]=\"draftTitle()\"\n (input)=\"onTitle($any($event.target).value)\"\n placeholder=\"VD: T\u00E3 gi\u1EA5y\"\n />\n } @else {\n <p class=\"go-readonly\">{{ draftTitle() }}</p>\n }\n </label>\n\n <!-- M\u00F4 t\u1EA3 -->\n @if (canEditTitleDesc || draftDesc()) {\n <label class=\"go-field\">\n <span class=\"go-field__label\">M\u00F4 t\u1EA3</span>\n @if (canEditTitleDesc) {\n <textarea\n class=\"go-input go-input--area\"\n rows=\"2\"\n [value]=\"draftDesc()\"\n (input)=\"onDesc($any($event.target).value)\"\n placeholder=\"S\u1ED1 l\u01B0\u1EE3ng, l\u01B0u \u00FD khi mang theo\u2026\"\n ></textarea>\n } @else {\n <p class=\"go-readonly go-readonly--muted\">{{ draftDesc() }}</p>\n }\n </label>\n }\n\n <!-- Ghi ch\u00FA (ch\u1EC9 trang checklist) -->\n @if (showNote) {\n <label class=\"go-field\">\n <span class=\"go-field__label\">Ghi ch\u00FA chuy\u1EBFn \u0111i</span>\n <textarea\n class=\"go-input go-input--area\"\n rows=\"2\"\n [value]=\"draftNote()\"\n (input)=\"onNote($any($event.target).value)\"\n placeholder=\"Ghi ch\u00FA ri\u00EAng cho chuy\u1EBFn n\u00E0y\u2026\"\n ></textarea>\n </label>\n }\n\n <!-- Tr\u1EA1ng th\u00E1i (ch\u1EC9 trang checklist) -->\n @if (showStatus) {\n <div class=\"go-field\">\n <span class=\"go-field__label\">Tr\u1EA1ng th\u00E1i</span>\n <div\n class=\"go-status-lg\"\n role=\"radiogroup\"\n aria-label=\"Tr\u1EA1ng th\u00E1i\"\n [class.go-status-lg--disabled]=\"draftNotNeeded()\"\n >\n @for (s of visibleStatuses(); track s.id) {\n <button\n type=\"button\"\n class=\"go-status-lg__seg\"\n [class.go-status-lg__seg--active]=\"draftStatus() === s.id && !draftNotNeeded()\"\n (click)=\"pickStatus(s.id)\"\n [attr.aria-pressed]=\"draftStatus() === s.id && !draftNotNeeded()\"\n [disabled]=\"draftNotNeeded()\"\n >\n <i [class]=\"s.icon\" aria-hidden=\"true\"></i>\n <span>{{ s.label }}</span>\n </button>\n }\n </div>\n </div>\n }\n\n @if (showNotNeeded) {\n <label class=\"go-toggle-row\">\n <span class=\"go-toggle-row__text\">Kh\u00F4ng c\u1EA7n mang l\u1EA7n n\u00E0y</span>\n <input\n type=\"checkbox\"\n role=\"switch\"\n [checked]=\"draftNotNeeded()\"\n (change)=\"setNotNeeded($any($event.target).checked)\"\n />\n </label>\n }\n\n <footer class=\"go-sheet__foot\">\n @if (showRemoveFromTrip) {\n <button type=\"button\" class=\"go-btn go-btn--danger\" (click)=\"removeFromTrip.emit()\">\n B\u1ECF kh\u1ECFi chuy\u1EBFn\n </button>\n }\n <span class=\"go-sheet__foot-spacer\"></span>\n <button type=\"button\" class=\"go-btn go-btn--ghost\" (click)=\"cancel.emit()\">Hu\u1EF7</button>\n <button type=\"button\" class=\"go-btn go-btn--primary\" (click)=\"emitSave()\">\n {{ mode === 'add' ? 'Th\u00EAm' : 'L\u01B0u' }}\n </button>\n </footer>\n </div>\n</div>\n", styles: [":host{--bg: #f6f7fb;--surface: #ffffff;--border: #e6e8ef;--primary: #5b6cff;--primary-soft: #eef0ff;--text: #0f172a;--muted: #475569;--muted-2: #94a3b8;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;color:var(--text)}.go-overlay{position:fixed;inset:0;z-index:420;background:#0f172a73;display:flex;align-items:center;justify-content:center;padding:16px;padding-top:calc(16px + env(safe-area-inset-top,0px));padding-bottom:calc(16px + env(safe-area-inset-bottom,0px));animation:go-fade .18s ease-out}@keyframes go-fade{0%{opacity:0}to{opacity:1}}.go-sheet{width:100%;max-width:460px;max-height:min(85vh,640px);overflow:auto;background:var(--surface);border-radius:18px;padding:18px 16px calc(16px + env(safe-area-inset-bottom,0px));display:flex;flex-direction:column;gap:14px;animation:go-sheet-in .22s cubic-bezier(.22,1,.36,1)}@keyframes go-sheet-in{0%{transform:translateY(12px);opacity:0}to{transform:translateY(0);opacity:1}}.go-sheet__head{display:flex;align-items:center;gap:8px}.go-sheet__title{margin:0;font-size:16px;font-weight:700;flex:1}.go-icon-btn{width:32px;height:32px;border:none;background:transparent;border-radius:9px;color:var(--muted);font-size:15px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;-webkit-tap-highlight-color:transparent}.go-icon-btn:hover{background:var(--primary-soft);color:var(--primary)}.go-field{display:flex;flex-direction:column;gap:6px}.go-field__label{font-size:12px;font-weight:600;color:var(--muted)}.go-input{width:100%;padding:10px 12px;border:1px solid var(--border);border-radius:10px;background:var(--bg);color:var(--text);font-size:15px;font-family:inherit;outline:none;transition:border-color .15s}.go-input:focus{border-color:var(--primary)}.go-input--area{resize:vertical;min-height:56px}.go-readonly{margin:0;font-size:15px;font-weight:600;color:var(--text)}.go-readonly--muted{font-weight:400;font-size:14px;color:var(--muted)}.go-status-lg{display:flex;gap:6px;border:1px solid var(--border);border-radius:12px;padding:4px;background:var(--bg)}.go-status-lg--disabled{opacity:.5}.go-status-lg__seg{flex:1;display:flex;flex-direction:column;align-items:center;gap:4px;padding:8px 4px;border:none;background:transparent;border-radius:9px;color:var(--muted);font-size:12px;font-weight:600;cursor:pointer;-webkit-tap-highlight-color:transparent}.go-status-lg__seg i{font-size:16px}.go-status-lg__seg--active{background:var(--primary);color:#fff}.go-toggle-row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 12px;border:1px solid var(--border);border-radius:12px;background:var(--bg);cursor:pointer}.go-toggle-row__text{font-size:14px;font-weight:600;color:var(--text)}.go-sheet__foot{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin-top:2px}.go-sheet__foot-spacer{flex:1}.go-btn{border:none;border-radius:10px;padding:9px 16px;font-size:14px;font-weight:600;cursor:pointer;-webkit-tap-highlight-color:transparent}.go-btn--ghost{background:transparent;color:var(--muted)}.go-btn--primary{background:var(--primary);color:#fff}.go-btn--danger{background:transparent;color:#dc2626;padding-left:0}\n"] }]
|
|
21577
21840
|
}], propDecorators: { mode: [{
|
|
21578
21841
|
type: Input
|
|
21579
21842
|
}], heading: [{
|
|
@@ -21582,6 +21845,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
21582
21845
|
type: Input
|
|
21583
21846
|
}], showNote: [{
|
|
21584
21847
|
type: Input
|
|
21848
|
+
}], showNotNeeded: [{
|
|
21849
|
+
type: Input
|
|
21585
21850
|
}], canEditTitleDesc: [{
|
|
21586
21851
|
type: Input
|
|
21587
21852
|
}], allowPartial: [{
|
|
@@ -21594,10 +21859,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
21594
21859
|
type: Input
|
|
21595
21860
|
}], initialStatus: [{
|
|
21596
21861
|
type: Input
|
|
21862
|
+
}], initialNotNeeded: [{
|
|
21863
|
+
type: Input
|
|
21864
|
+
}], showRemoveFromTrip: [{
|
|
21865
|
+
type: Input
|
|
21597
21866
|
}], cancel: [{
|
|
21598
21867
|
type: Output
|
|
21599
21868
|
}], save: [{
|
|
21600
21869
|
type: Output
|
|
21870
|
+
}], removeFromTrip: [{
|
|
21871
|
+
type: Output
|
|
21601
21872
|
}] } });
|
|
21602
21873
|
|
|
21603
21874
|
class GoingOutChecklistComponent {
|
|
@@ -21619,6 +21890,9 @@ class GoingOutChecklistComponent {
|
|
|
21619
21890
|
this.allowAdd = this.svc.allowAdd;
|
|
21620
21891
|
this.allowPartial = this.svc.allowPartial;
|
|
21621
21892
|
this.sheetLoading = this.svc.sheetLoading;
|
|
21893
|
+
this.activeTripCount = this.svc.activeTripCount;
|
|
21894
|
+
this.packedCount = this.svc.packedCount;
|
|
21895
|
+
this.progressPercent = this.svc.progressPercent;
|
|
21622
21896
|
this.title = computed(() => {
|
|
21623
21897
|
const name = this.babyName().trim();
|
|
21624
21898
|
return name ? `Chuẩn bị cho ${name} ra ngoài` : 'Chuẩn bị đi ra ngoài';
|
|
@@ -21626,10 +21900,22 @@ class GoingOutChecklistComponent {
|
|
|
21626
21900
|
this.editingItem = signal(null, ...(ngDevMode ? [{ debugName: "editingItem" }] : /* istanbul ignore next */ []));
|
|
21627
21901
|
this.adding = signal(false, ...(ngDevMode ? [{ debugName: "adding" }] : /* istanbul ignore next */ []));
|
|
21628
21902
|
this.resetConfirm = signal(false, ...(ngDevMode ? [{ debugName: "resetConfirm" }] : /* istanbul ignore next */ []));
|
|
21903
|
+
this.headerMenuOpen = signal(false, ...(ngDevMode ? [{ debugName: "headerMenuOpen" }] : /* istanbul ignore next */ []));
|
|
21904
|
+
this.draggingId = signal(null, ...(ngDevMode ? [{ debugName: "draggingId" }] : /* istanbul ignore next */ []));
|
|
21905
|
+
this.dragOverId = signal(null, ...(ngDevMode ? [{ debugName: "dragOverId" }] : /* istanbul ignore next */ []));
|
|
21629
21906
|
this.isDialogOpen = computed(() => this.editingItem() !== null || this.adding(), ...(ngDevMode ? [{ debugName: "isDialogOpen" }] : /* istanbul ignore next */ []));
|
|
21630
21907
|
this.dialogMode = computed(() => (this.adding() ? 'add' : 'edit'), ...(ngDevMode ? [{ debugName: "dialogMode" }] : /* istanbul ignore next */ []));
|
|
21631
|
-
this.dialogHeading = computed(() =>
|
|
21908
|
+
this.dialogHeading = computed(() => this.adding() ? 'Thêm món cho chuyến này' : 'Chi tiết món', ...(ngDevMode ? [{ debugName: "dialogHeading" }] : /* istanbul ignore next */ []));
|
|
21632
21909
|
this.dialogCanEditTitleDesc = computed(() => this.adding() || this.allowEdit(), ...(ngDevMode ? [{ debugName: "dialogCanEditTitleDesc" }] : /* istanbul ignore next */ []));
|
|
21910
|
+
this.dialogShowRemove = computed(() => !this.adding() && this.allowEdit() && this.editingItem() !== null, ...(ngDevMode ? [{ debugName: "dialogShowRemove" }] : /* istanbul ignore next */ []));
|
|
21911
|
+
this.heroTitle = computed(() => {
|
|
21912
|
+
if (this.allDone())
|
|
21913
|
+
return 'Sẵn sàng — đi thôi!';
|
|
21914
|
+
const pct = this.progressPercent();
|
|
21915
|
+
if (pct >= 70)
|
|
21916
|
+
return 'Gần xong rồi!';
|
|
21917
|
+
return 'Đang chuẩn bị…';
|
|
21918
|
+
}, ...(ngDevMode ? [{ debugName: "heroTitle" }] : /* istanbul ignore next */ []));
|
|
21633
21919
|
this.statusLabels = {
|
|
21634
21920
|
none: 'Chưa chuẩn bị',
|
|
21635
21921
|
partial: 'Đang chuẩn bị 1 phần',
|
|
@@ -21659,13 +21945,69 @@ class GoingOutChecklistComponent {
|
|
|
21659
21945
|
const next = order[(order.indexOf(item.status) % order.length + 1) % order.length];
|
|
21660
21946
|
this.svc.setTripStatus(item.id, next);
|
|
21661
21947
|
}
|
|
21662
|
-
toggleNotNeeded(item, ev) {
|
|
21663
|
-
ev.stopPropagation();
|
|
21664
|
-
this.svc.toggleNotNeeded(item.id);
|
|
21665
|
-
}
|
|
21666
21948
|
statusLabel(status) {
|
|
21667
21949
|
return this.statusLabels[status];
|
|
21668
21950
|
}
|
|
21951
|
+
itemStatusBadge(item) {
|
|
21952
|
+
if (item.notNeeded)
|
|
21953
|
+
return 'Không mang';
|
|
21954
|
+
if (item.status === 'done')
|
|
21955
|
+
return 'Đã gói';
|
|
21956
|
+
if (item.status === 'partial')
|
|
21957
|
+
return 'Đang làm';
|
|
21958
|
+
return 'Chưa';
|
|
21959
|
+
}
|
|
21960
|
+
toggleHeaderMenu() {
|
|
21961
|
+
this.headerMenuOpen.update((v) => !v);
|
|
21962
|
+
}
|
|
21963
|
+
closeHeaderMenu() {
|
|
21964
|
+
this.headerMenuOpen.set(false);
|
|
21965
|
+
}
|
|
21966
|
+
onManageFromMenu() {
|
|
21967
|
+
this.closeHeaderMenu();
|
|
21968
|
+
this.manage.emit();
|
|
21969
|
+
}
|
|
21970
|
+
onResetFromMenu() {
|
|
21971
|
+
this.closeHeaderMenu();
|
|
21972
|
+
this.askReset();
|
|
21973
|
+
}
|
|
21974
|
+
moveItem(index, delta, ev) {
|
|
21975
|
+
ev.stopPropagation();
|
|
21976
|
+
this.svc.moveTripItem(index, delta);
|
|
21977
|
+
}
|
|
21978
|
+
onDragStart(ev, item) {
|
|
21979
|
+
this.draggingId.set(item.id);
|
|
21980
|
+
ev.dataTransfer?.setData('text/plain', item.id);
|
|
21981
|
+
if (ev.dataTransfer) {
|
|
21982
|
+
ev.dataTransfer.effectAllowed = 'move';
|
|
21983
|
+
}
|
|
21984
|
+
}
|
|
21985
|
+
onDragOver(ev, item) {
|
|
21986
|
+
ev.preventDefault();
|
|
21987
|
+
if (this.draggingId() !== item.id) {
|
|
21988
|
+
this.dragOverId.set(item.id);
|
|
21989
|
+
}
|
|
21990
|
+
}
|
|
21991
|
+
onDrop(ev, item) {
|
|
21992
|
+
ev.preventDefault();
|
|
21993
|
+
const fromId = this.draggingId() ?? ev.dataTransfer?.getData('text/plain');
|
|
21994
|
+
if (fromId) {
|
|
21995
|
+
this.svc.reorderTrip(fromId, item.id);
|
|
21996
|
+
}
|
|
21997
|
+
this.onDragEnd();
|
|
21998
|
+
}
|
|
21999
|
+
onDragEnd() {
|
|
22000
|
+
this.draggingId.set(null);
|
|
22001
|
+
this.dragOverId.set(null);
|
|
22002
|
+
}
|
|
22003
|
+
openDetail(item, ev) {
|
|
22004
|
+
ev.stopPropagation();
|
|
22005
|
+
this.openEdit(item);
|
|
22006
|
+
}
|
|
22007
|
+
onMainTap(item, ev) {
|
|
22008
|
+
ev.stopPropagation();
|
|
22009
|
+
this.cycleStatus(item);
|
|
22010
|
+
}
|
|
21669
22011
|
openAdd() {
|
|
21670
22012
|
this.editingItem.set(null);
|
|
21671
22013
|
this.adding.set(true);
|
|
@@ -21680,7 +22022,7 @@ class GoingOutChecklistComponent {
|
|
|
21680
22022
|
}
|
|
21681
22023
|
onSaveEdit(payload) {
|
|
21682
22024
|
if (this.adding()) {
|
|
21683
|
-
this.svc.addChecklistItem(payload.title, payload.desc, payload.note, payload.status);
|
|
22025
|
+
this.svc.addChecklistItem(payload.title, payload.desc, payload.note, payload.status, payload.notNeeded);
|
|
21684
22026
|
}
|
|
21685
22027
|
else {
|
|
21686
22028
|
const item = this.editingItem();
|
|
@@ -21690,14 +22032,14 @@ class GoingOutChecklistComponent {
|
|
|
21690
22032
|
}
|
|
21691
22033
|
this.closeDialog();
|
|
21692
22034
|
}
|
|
21693
|
-
|
|
21694
|
-
|
|
21695
|
-
|
|
21696
|
-
if (!this.allowEdit())
|
|
22035
|
+
removeFromDialog() {
|
|
22036
|
+
const item = this.editingItem();
|
|
22037
|
+
if (!item || !this.allowEdit())
|
|
21697
22038
|
return;
|
|
21698
22039
|
if (!confirm(`Bỏ "${item.title}" khỏi checklist lần này?`))
|
|
21699
22040
|
return;
|
|
21700
22041
|
this.svc.removeFromTrip(item.id);
|
|
22042
|
+
this.closeDialog();
|
|
21701
22043
|
}
|
|
21702
22044
|
askReset() {
|
|
21703
22045
|
this.resetConfirm.set(true);
|
|
@@ -21710,11 +22052,11 @@ class GoingOutChecklistComponent {
|
|
|
21710
22052
|
this.resetConfirm.set(false);
|
|
21711
22053
|
}
|
|
21712
22054
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: GoingOutChecklistComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
21713
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: GoingOutChecklistComponent, isStandalone: true, selector: "app-going-out-checklist", inputs: { babyName: { classPropertyName: "babyName", publicName: "babyName", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { back: "back", manage: "manage" }, ngImport: i0, template: "<section class=\"go-root\" role=\"dialog\" aria-modal=\"true\" [attr.aria-label]=\"title()\">\n <header class=\"go-header\">\n <button type=\"button\" class=\"go-back\" (click)=\"back.emit()\" aria-label=\"Quay l\u1EA1i\">\n <i class=\"pi pi-chevron-left\" aria-hidden=\"true\"></i>\n </button>\n <h1 class=\"go-header__title\">{{ title() }}</h1>\n <span class=\"go-header__spacer\"></span>\n <button\n type=\"button\"\n class=\"go-header-btn\"\n (click)=\"refresh()\"\n [disabled]=\"sheetLoading()\"\n title=\"L\u1EA5y checklist m\u1EDBi nh\u1EA5t t\u1EEB Google Sheet (\u0111\u1ED3ng b\u1ED9 v\u1EDBi ng\u01B0\u1EDDi kh\u00E1c)\"\n aria-label=\"\u0110\u1ED3ng b\u1ED9 t\u1EEB Google Sheet\"\n >\n <i class=\"pi pi-cloud-download\" [class.go-header-btn__spin]=\"sheetLoading()\" aria-hidden=\"true\"></i>\n <span>\u0110\u1ED3ng b\u1ED9</span>\n </button>\n <button\n type=\"button\"\n class=\"go-header-btn go-header-btn--muted\"\n (click)=\"askReset()\"\n title=\"\u0110\u1EB7t l\u1EA1i checklist: tick, ghi ch\u00FA, m\u00F3n \u0111\u00E3 \u1EA9n ho\u1EB7c th\u00EAm t\u1EA1m\"\n aria-label=\"\u0110\u1EB7t l\u1EA1i m\u1EB7c \u0111\u1ECBnh\"\n >\n <i class=\"pi pi-undo\" aria-hidden=\"true\"></i>\n <span>\u0110\u1EB7t l\u1EA1i m\u1EB7c \u0111\u1ECBnh</span>\n </button>\n <button\n type=\"button\"\n class=\"go-icon-btn\"\n (click)=\"manage.emit()\"\n title=\"Qu\u1EA3n l\u00FD danh s\u00E1ch m\u00F3n m\u1EABu\"\n aria-label=\"Qu\u1EA3n l\u00FD danh s\u00E1ch\"\n >\n <i class=\"pi pi-list\" aria-hidden=\"true\"></i>\n </button>\n </header>\n\n @if (allDone()) {\n <div class=\"go-ready\" role=\"status\">\n <i class=\"pi pi-check-circle\" aria-hidden=\"true\"></i>\n <span>S\u1EB5n s\u00E0ng \u2014 mang \u0111\u1ED3 \u0111i th\u00F4i</span>\n </div>\n }\n\n <div class=\"go-progress\">\n <div class=\"go-progress__row\">\n <span class=\"go-progress__count\">{{ doneCount() }}/{{ total() }}</span>\n <div class=\"go-progress__meta\">\n <span class=\"go-progress__state\" [class.go-progress__state--done]=\"allDone()\">\n {{ allDone() ? 'S\u1EB5n s\u00E0ng \u0111i r\u1ED3i!' : '\u0110ang chu\u1EA9n b\u1ECB\u2026' }}\n </span>\n <span class=\"go-progress__sub\">\n {{ readyCount() }} \u0111ang l\u00E0m \u00B7 {{ total() - readyCount() }} ch\u01B0a b\u1EAFt \u0111\u1EA7u\n @if (notNeededCount() > 0) {\n \u00B7 {{ notNeededCount() }} kh\u00F4ng c\u1EA7n\n }\n </span>\n </div>\n </div>\n <div class=\"go-bar\"><span class=\"go-bar__fill\" [style.width.%]=\"total() ? (doneCount() / total()) * 100 : 0\"></span></div>\n </div>\n\n <div class=\"go-list\">\n @for (item of items(); track item.id) {\n <div\n class=\"go-row\"\n [class.go-row--not-needed]=\"item.notNeeded\"\n (click)=\"cycleStatus(item)\"\n >\n <button\n type=\"button\"\n class=\"go-check\"\n [class.go-check--none]=\"item.status === 'none' && !item.notNeeded\"\n [class.go-check--partial]=\"item.status === 'partial'\"\n [class.go-check--done]=\"item.status === 'done'\"\n [class.go-check--skipped]=\"item.notNeeded\"\n (click)=\"cycleStatus(item); $event.stopPropagation()\"\n [attr.aria-label]=\"item.notNeeded ? 'Kh\u00F4ng c\u1EA7n l\u1EA7n n\u00E0y' : statusLabel(item.status)\"\n [attr.aria-pressed]=\"item.notNeeded || item.status !== 'none'\"\n [disabled]=\"item.notNeeded\"\n >\n @if (!item.notNeeded && item.status === 'done') {\n <i class=\"pi pi-check\" aria-hidden=\"true\"></i>\n }\n </button>\n\n <div class=\"go-row__body\" (click)=\"cycleStatus(item); $event.stopPropagation()\">\n <span\n class=\"go-row__title\"\n [class.go-row__title--done]=\"item.status === 'done'\"\n [class.go-row__title--skipped]=\"item.notNeeded\"\n >{{ item.title }}</span>\n @if (item.desc) {\n <span class=\"go-row__desc\">{{ item.desc }}</span>\n }\n @if (item.note) {\n <span class=\"go-row__note\">\n <i class=\"pi pi-file-edit\" aria-hidden=\"true\"></i>\n {{ item.note }}\n </span>\n }\n </div>\n\n <button\n type=\"button\"\n class=\"go-icon-btn go-row__skip\"\n [class.go-row__skip--on]=\"item.notNeeded\"\n (click)=\"toggleNotNeeded(item, $event)\"\n [title]=\"item.notNeeded ? 'C\u1EA7n mang l\u1EA1i l\u1EA7n n\u00E0y' : 'Kh\u00F4ng c\u1EA7n mang l\u1EA7n n\u00E0y'\"\n [attr.aria-label]=\"item.notNeeded ? 'C\u1EA7n mang l\u1EA1i l\u1EA7n n\u00E0y' : 'Kh\u00F4ng c\u1EA7n mang l\u1EA7n n\u00E0y'\"\n [attr.aria-pressed]=\"item.notNeeded\"\n >\n <i class=\"pi pi-ban\" aria-hidden=\"true\"></i>\n </button>\n\n <button\n type=\"button\"\n class=\"go-icon-btn go-row__edit\"\n (click)=\"openEdit(item); $event.stopPropagation()\"\n title=\"Ghi ch\u00FA / s\u1EEDa\"\n aria-label=\"Ghi ch\u00FA / s\u1EEDa\"\n >\n <i class=\"pi pi-pencil\" aria-hidden=\"true\"></i>\n </button>\n\n @if (allowEdit()) {\n <button\n type=\"button\"\n class=\"go-icon-btn go-row__danger\"\n (click)=\"removeItem(item, $event)\"\n title=\"B\u1ECF kh\u1ECFi checklist l\u1EA7n n\u00E0y\"\n aria-label=\"B\u1ECF kh\u1ECFi checklist l\u1EA7n n\u00E0y\"\n >\n <i class=\"pi pi-trash\" aria-hidden=\"true\"></i>\n </button>\n }\n </div>\n } @empty {\n <p class=\"go-empty\">Ch\u01B0a c\u00F3 m\u00F3n n\u00E0o.</p>\n }\n </div>\n\n @if (allowAdd()) {\n <div class=\"go-add-wrap\">\n <button type=\"button\" class=\"go-add\" (click)=\"openAdd()\">\n <i class=\"pi pi-plus\" aria-hidden=\"true\"></i>\n <span>Th\u00EAm m\u00F3n</span>\n </button>\n </div>\n }\n</section>\n\n@if (isDialogOpen()) {\n <app-going-out-edit-dialog\n [heading]=\"dialogHeading()\"\n [mode]=\"dialogMode()\"\n [showStatus]=\"true\"\n [showNote]=\"true\"\n [canEditTitleDesc]=\"dialogCanEditTitleDesc()\"\n [allowPartial]=\"allowPartial()\"\n [initialTitle]=\"editingItem()?.title ?? ''\"\n [initialDesc]=\"editingItem()?.desc ?? ''\"\n [initialNote]=\"editingItem()?.note ?? ''\"\n [initialStatus]=\"editingItem()?.status ?? 'none'\"\n (cancel)=\"closeDialog()\"\n (save)=\"onSaveEdit($event)\"\n />\n}\n\n@if (resetConfirm()) {\n <div class=\"go-overlay\" (click)=\"cancelReset()\" role=\"presentation\">\n <div class=\"go-confirm\" role=\"alertdialog\" aria-modal=\"true\" aria-label=\"\u0110\u1EB7t l\u1EA1i m\u1EB7c \u0111\u1ECBnh\" (click)=\"$event.stopPropagation()\">\n <h2 class=\"go-confirm__title\">\u0110\u1EB7t l\u1EA1i m\u1EB7c \u0111\u1ECBnh?</h2>\n <p class=\"go-confirm__body\">\n Checklist s\u1EBD tr\u1EDF l\u1EA1i nh\u01B0 danh s\u00E1ch m\u1EABu: b\u1ECF tick, xo\u00E1 ghi ch\u00FA, b\u1ECF \u0111\u00E1nh d\u1EA5u kh\u00F4ng c\u1EA7n, hi\u1EC7n l\u1EA1i m\u00F3n \u0111\u00E3 \u1EA9n v\u00E0 b\u1ECF m\u00F3n ch\u1EC9 th\u00EAm cho l\u1EA7n \u0111i n\u00E0y.\n Danh s\u00E1ch m\u1EABu trong c\u00E0i \u0111\u1EB7t kh\u00F4ng thay \u0111\u1ED5i.\n </p>\n <div class=\"go-confirm__foot\">\n <button type=\"button\" class=\"go-btn go-btn--ghost\" (click)=\"cancelReset()\">Hu\u1EF7</button>\n <button type=\"button\" class=\"go-btn go-btn--primary\" (click)=\"confirmReset()\">\u0110\u1EB7t l\u1EA1i m\u1EB7c \u0111\u1ECBnh</button>\n </div>\n </div>\n </div>\n}\n", styles: ["@charset \"UTF-8\";:host{--bg: #f6f7fb;--surface: #ffffff;--border: #e6e8ef;--primary: #5b6cff;--primary-soft: #eef0ff;--text: #0f172a;--muted: #475569;--muted-2: #94a3b8;position:fixed;inset:0;z-index:200;background:var(--bg);display:flex;flex-direction:column;color:var(--text);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;animation:go-slide .22s cubic-bezier(.22,1,.36,1)}@keyframes go-slide{0%{transform:translate(8px);opacity:0}to{transform:translate(0);opacity:1}}.go-root{display:flex;flex-direction:column;flex:1;min-height:0}.go-header{flex-shrink:0;display:flex;align-items:center;gap:6px;padding:8px 10px;padding-top:calc(8px + env(safe-area-inset-top,0px));background:var(--surface);border-bottom:1px solid var(--border)}.go-back{width:32px;height:32px;border:none;background:transparent;border-radius:9px;color:var(--muted);font-size:15px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;-webkit-tap-highlight-color:transparent}.go-back:hover{background:var(--primary-soft);color:var(--primary)}.go-header__title{margin:0;font-size:16px;font-weight:700;line-height:1.15;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.go-header__spacer{flex:1}.go-header-btn{flex-shrink:0;display:inline-flex;align-items:center;gap:4px;height:30px;padding:0 8px;border:1px solid var(--border);border-radius:8px;background:var(--surface);color:var(--primary);font-size:11px;font-weight:700;cursor:pointer;-webkit-tap-highlight-color:transparent}.go-header-btn i{font-size:12px}.go-header-btn:hover:not(:disabled){background:var(--primary-soft);border-color:color-mix(in srgb,var(--primary) 35%,var(--border))}.go-header-btn:disabled{opacity:.55;cursor:default}.go-header-btn--muted{color:var(--muted)}.go-header-btn--muted:hover:not(:disabled){background:#f1f5f9;border-color:var(--border);color:var(--text)}.go-header-btn__spin{animation:go-refresh-spin .8s linear infinite}.go-icon-btn{width:32px;height:32px;border:none;background:transparent;border-radius:9px;color:var(--muted);font-size:15px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;-webkit-tap-highlight-color:transparent}.go-icon-btn:hover:not(:disabled){background:var(--primary-soft);color:var(--primary)}.go-icon-btn:disabled{opacity:.55;cursor:default}.go-icon-btn__spin{animation:go-refresh-spin .8s linear infinite}@keyframes go-refresh-spin{to{transform:rotate(360deg)}}.go-ready{display:flex;align-items:center;gap:8px;margin:12px;padding:8px 14px;background:#e9f9ef;border:1px solid #b6e7c4;border-radius:12px;color:#15803d;font-size:13px;font-weight:600}.go-ready i{font-size:16px}.go-progress{margin:0 12px 10px;padding:12px 14px;background:var(--surface);border:1px solid var(--border);border-radius:14px}.go-progress__row{display:flex;align-items:center;gap:12px;margin-bottom:10px}.go-progress__count{font-size:22px;font-weight:800;color:var(--text);line-height:1}.go-progress__meta{display:flex;flex-direction:column;gap:2px}.go-progress__state{font-size:14px;font-weight:700}.go-progress__state--done{color:#16a34a}.go-progress__sub{font-size:12px;color:var(--muted)}.go-bar{height:6px;border-radius:999px;background:var(--border);overflow:hidden}.go-bar__fill{display:block;height:100%;background:var(--primary);border-radius:999px;transition:width .25s ease}.go-list{flex:1;min-height:0;overflow:auto;-webkit-overflow-scrolling:touch;padding:4px 12px calc(16px + env(safe-area-inset-bottom,0px));display:flex;flex-direction:column;gap:8px}.go-row{display:flex;align-items:flex-start;gap:10px;padding:12px;background:var(--surface);border:1px solid var(--border);border-radius:14px;cursor:pointer;-webkit-tap-highlight-color:transparent}.go-row:active{background:var(--primary-soft)}.go-row--not-needed{background:#f8fafc;border-color:#e2e8f0}.go-row--not-needed:active{background:#f1f5f9}.go-check{flex-shrink:0;width:30px;height:30px;margin-top:1px;padding:0;border:2px solid var(--border);border-radius:50%;background:var(--surface);color:#fff;font-size:14px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;position:relative;transition:border-color .15s,background .15s,transform .1s;-webkit-tap-highlight-color:transparent}.go-check:active{transform:scale(.9)}.go-check--none{border-color:var(--muted-2)}.go-check--partial{border-color:#f59e0b}.go-check--partial:before{content:\"\";position:absolute;inset:0;border-radius:50%;background:linear-gradient(to right,#f59e0b 50%,transparent 50%)}.go-check--partial i{position:relative;z-index:1}.go-check--done{border-color:var(--primary);background:var(--primary)}.go-check--skipped{border-color:var(--muted-2);background:#f1f5f9;color:var(--muted);cursor:default;opacity:.95}.go-row__body{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.go-row__title{font-size:15px;font-weight:700;color:var(--text);word-break:break-word}.go-row__title--done{color:var(--muted);text-decoration:line-through}.go-row__title--skipped{color:var(--muted-2);text-decoration:line-through;font-weight:600}.go-row__desc{font-size:13px;color:var(--muted);word-break:break-word}.go-row__note{display:flex;align-items:flex-start;gap:6px;margin-top:4px;font-size:12px;color:var(--primary);background:var(--primary-soft);border-radius:8px;padding:4px 8px}.go-row__note i{font-size:12px;margin-top:1px}.go-row__skip{flex-shrink:0;color:var(--muted-2)}.go-row__skip--on{color:#64748b;background:#e2e8f0}.go-row__skip:hover{background:#f1f5f9;color:var(--muted)}.go-row__skip--on:hover{background:#cbd5e1;color:var(--text)}.go-row__edit{flex-shrink:0}.go-row__danger{flex-shrink:0;color:var(--danger, #ef4444)}.go-row__danger:hover{background:#fee2e2;color:#ef4444}.go-empty{margin:24px 12px;text-align:center;font-size:14px;color:var(--muted)}.go-add-wrap{flex-shrink:0;padding:8px 12px calc(16px + env(safe-area-inset-bottom,0px))}.go-add{width:100%;border:1px dashed var(--primary);background:var(--primary-soft);color:var(--primary);border-radius:14px;padding:12px;font-size:15px;font-weight:700;display:inline-flex;align-items:center;justify-content:center;gap:8px;cursor:pointer;-webkit-tap-highlight-color:transparent}.go-overlay{position:fixed;inset:0;z-index:420;background:#0f172a80;display:flex;align-items:center;justify-content:center;padding:16px;animation:go-fade .18s ease-out}@keyframes go-fade{0%{opacity:0}to{opacity:1}}.go-confirm{width:100%;max-width:360px;background:var(--surface);border-radius:16px;padding:18px;animation:go-sheet-in .2s cubic-bezier(.22,1,.36,1)}@keyframes go-sheet-in{0%{transform:translateY(12px);opacity:0}to{transform:translateY(0);opacity:1}}.go-confirm__title{margin:0 0 6px;font-size:17px;font-weight:700}.go-confirm__body{margin:0 0 16px;font-size:14px;color:var(--muted)}.go-confirm__foot{display:flex;justify-content:flex-end;gap:8px}.go-btn{border:none;border-radius:10px;padding:9px 16px;font-size:14px;font-weight:600;cursor:pointer;-webkit-tap-highlight-color:transparent}.go-btn--ghost{background:transparent;color:var(--muted)}.go-btn--primary{background:var(--primary);color:#fff}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: GoingOutEditDialogComponent, selector: "app-going-out-edit-dialog", inputs: ["mode", "heading", "showStatus", "showNote", "canEditTitleDesc", "allowPartial", "initialTitle", "initialDesc", "initialNote", "initialStatus"], outputs: ["cancel", "save"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
22055
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: GoingOutChecklistComponent, isStandalone: true, selector: "app-going-out-checklist", inputs: { babyName: { classPropertyName: "babyName", publicName: "babyName", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { back: "back", manage: "manage" }, ngImport: i0, template: "<section class=\"go-root\" role=\"dialog\" aria-modal=\"true\" [attr.aria-label]=\"title()\">\n <header class=\"go-header\">\n <button type=\"button\" class=\"go-back\" (click)=\"back.emit()\" aria-label=\"Quay l\u1EA1i\">\n <i class=\"pi pi-chevron-left\" aria-hidden=\"true\"></i>\n </button>\n <h1 class=\"go-header__title\">{{ title() }}</h1>\n <span class=\"go-header__spacer\"></span>\n <button\n type=\"button\"\n class=\"go-icon-btn\"\n (click)=\"refresh()\"\n [disabled]=\"sheetLoading()\"\n title=\"\u0110\u1ED3ng b\u1ED9 t\u1EEB Google Sheet\"\n aria-label=\"\u0110\u1ED3ng b\u1ED9 t\u1EEB Google Sheet\"\n >\n <i class=\"pi pi-cloud-download\" [class.go-icon-btn__spin]=\"sheetLoading()\" aria-hidden=\"true\"></i>\n </button>\n <div class=\"go-header-menu\">\n <button\n type=\"button\"\n class=\"go-icon-btn\"\n (click)=\"toggleHeaderMenu()\"\n aria-label=\"Th\u00EAm thao t\u00E1c\"\n aria-haspopup=\"menu\"\n [attr.aria-expanded]=\"headerMenuOpen()\"\n >\n <i class=\"pi pi-ellipsis-v\" aria-hidden=\"true\"></i>\n </button>\n @if (headerMenuOpen()) {\n <div class=\"go-header-menu__panel\" role=\"menu\">\n <button type=\"button\" role=\"menuitem\" class=\"go-header-menu__item\" (click)=\"onManageFromMenu()\">\n <i class=\"pi pi-list\" aria-hidden=\"true\"></i>\n Qu\u1EA3n l\u00FD danh s\u00E1ch m\u1EABu\n </button>\n <button type=\"button\" role=\"menuitem\" class=\"go-header-menu__item\" (click)=\"onResetFromMenu()\">\n <i class=\"pi pi-undo\" aria-hidden=\"true\"></i>\n \u0110\u1EB7t l\u1EA1i m\u1EB7c \u0111\u1ECBnh\n </button>\n </div>\n }\n </div>\n </header>\n\n @if (headerMenuOpen()) {\n <button type=\"button\" class=\"go-header-menu__backdrop\" aria-label=\"\u0110\u00F3ng menu\" (click)=\"closeHeaderMenu()\"></button>\n }\n\n <div\n class=\"go-hero\"\n [class.go-hero--done]=\"allDone()\"\n role=\"status\"\n aria-live=\"polite\"\n >\n <div class=\"go-hero__copy\">\n <span class=\"go-hero__eyebrow\">Ra ngo\u00E0i</span>\n <span class=\"go-hero__title\">{{ heroTitle() }}</span>\n <span class=\"go-hero__sub\">\n {{ packedCount() }}/{{ activeTripCount() }} m\u00F3n s\u1EB5n s\u00E0ng\n @if (notNeededCount() > 0) {\n \u00B7 {{ notNeededCount() }} kh\u00F4ng mang\n }\n </span>\n </div>\n <div\n class=\"go-hero__ring\"\n [class.go-hero__ring--done]=\"allDone()\"\n [attr.aria-label]=\"'Ti\u1EBFn \u0111\u1ED9 ' + progressPercent() + ' ph\u1EA7n tr\u0103m'\"\n >\n {{ progressPercent() }}%\n </div>\n </div>\n\n <p class=\"go-list-hint\">Ch\u1EA1m d\u00F2ng \u0111\u1EC3 tick \u00B7 K\u00E9o <i class=\"pi pi-bars\" aria-hidden=\"true\"></i> ho\u1EB7c \u2191\u2193 \u0111\u1EC3 s\u1EAFp x\u1EBFp (ch\u1EC9 tr\u00EAn m\u00E1y)</p>\n\n <div class=\"go-list\">\n @for (item of items(); track item.id; let i = $index; let last = $last) {\n <div\n class=\"go-row\"\n [class.go-row--not-needed]=\"item.notNeeded\"\n [class.go-row--dragging]=\"draggingId() === item.id\"\n [class.go-row--drag-over]=\"dragOverId() === item.id\"\n (dragover)=\"onDragOver($event, item)\"\n (drop)=\"onDrop($event, item)\"\n >\n <div class=\"go-row__sort\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"go-sort-btn\"\n [disabled]=\"i === 0\"\n (click)=\"moveItem(i, -1, $event)\"\n aria-label=\"\u0110\u01B0a m\u00F3n l\u00EAn\"\n >\n <i class=\"pi pi-angle-up\" aria-hidden=\"true\"></i>\n </button>\n <span\n class=\"go-row__grip\"\n draggable=\"true\"\n title=\"K\u00E9o \u0111\u1EC3 \u0111\u1ED5i th\u1EE9 t\u1EF1\"\n aria-label=\"K\u00E9o \u0111\u1EC3 \u0111\u1ED5i th\u1EE9 t\u1EF1\"\n (dragstart)=\"onDragStart($event, item)\"\n (dragend)=\"onDragEnd()\"\n >\n <i class=\"pi pi-bars\" aria-hidden=\"true\"></i>\n </span>\n <button\n type=\"button\"\n class=\"go-sort-btn\"\n [disabled]=\"last\"\n (click)=\"moveItem(i, 1, $event)\"\n aria-label=\"\u0110\u01B0a m\u00F3n xu\u1ED1ng\"\n >\n <i class=\"pi pi-angle-down\" aria-hidden=\"true\"></i>\n </button>\n </div>\n\n <button\n type=\"button\"\n class=\"go-check\"\n [class.go-check--none]=\"item.status === 'none' && !item.notNeeded\"\n [class.go-check--partial]=\"item.status === 'partial' && !item.notNeeded\"\n [class.go-check--done]=\"item.status === 'done' && !item.notNeeded\"\n [class.go-check--skipped]=\"item.notNeeded\"\n (click)=\"cycleStatus(item); $event.stopPropagation()\"\n [attr.aria-label]=\"item.notNeeded ? 'Kh\u00F4ng mang l\u1EA7n n\u00E0y' : statusLabel(item.status)\"\n [disabled]=\"item.notNeeded\"\n >\n @if (!item.notNeeded && item.status === 'done') {\n <i class=\"pi pi-check\" aria-hidden=\"true\"></i>\n }\n @if (!item.notNeeded && item.status === 'partial') {\n <span class=\"go-check__half\" aria-hidden=\"true\">\u00BD</span>\n }\n </button>\n\n <button\n type=\"button\"\n class=\"go-row__open\"\n (click)=\"onMainTap(item, $event)\"\n [attr.aria-label]=\"item.notNeeded ? item.title : '\u0110\u1ED5i tr\u1EA1ng th\u00E1i ' + item.title\"\n >\n <span class=\"go-row__title-row\">\n <span\n class=\"go-row__title\"\n [class.go-row__title--done]=\"item.status === 'done' && !item.notNeeded\"\n [class.go-row__title--skipped]=\"item.notNeeded\"\n >{{ item.title }}</span>\n <span class=\"go-row__badge\">{{ itemStatusBadge(item) }}</span>\n </span>\n @if (item.desc) {\n <span class=\"go-row__desc\">{{ item.desc }}</span>\n }\n @if (item.note) {\n <span class=\"go-row__note\">{{ item.note }}</span>\n }\n </button>\n\n <button\n type=\"button\"\n class=\"go-icon-btn go-row__detail\"\n (click)=\"openDetail(item, $event)\"\n title=\"Xem chi ti\u1EBFt\"\n aria-label=\"Xem chi ti\u1EBFt\"\n >\n <i class=\"pi pi-angle-right\" aria-hidden=\"true\"></i>\n </button>\n </div>\n } @empty {\n <p class=\"go-empty\">Ch\u01B0a c\u00F3 m\u00F3n n\u00E0o.</p>\n }\n </div>\n\n @if (allowAdd()) {\n <div class=\"go-add-wrap\">\n <button type=\"button\" class=\"go-add\" (click)=\"openAdd()\">\n <i class=\"pi pi-plus\" aria-hidden=\"true\"></i>\n <span>Th\u00EAm m\u00F3n cho chuy\u1EBFn n\u00E0y</span>\n </button>\n </div>\n }\n</section>\n\n@if (isDialogOpen()) {\n <app-going-out-edit-dialog\n [heading]=\"dialogHeading()\"\n [mode]=\"dialogMode()\"\n [showStatus]=\"true\"\n [showNote]=\"true\"\n [showNotNeeded]=\"true\"\n [canEditTitleDesc]=\"dialogCanEditTitleDesc()\"\n [allowPartial]=\"allowPartial()\"\n [showRemoveFromTrip]=\"dialogShowRemove()\"\n [initialTitle]=\"editingItem()?.title ?? ''\"\n [initialDesc]=\"editingItem()?.desc ?? ''\"\n [initialNote]=\"editingItem()?.note ?? ''\"\n [initialStatus]=\"editingItem()?.status ?? 'none'\"\n [initialNotNeeded]=\"editingItem()?.notNeeded ?? false\"\n (cancel)=\"closeDialog()\"\n (save)=\"onSaveEdit($event)\"\n (removeFromTrip)=\"removeFromDialog()\"\n />\n}\n\n@if (resetConfirm()) {\n <div class=\"go-overlay\" (click)=\"cancelReset()\" role=\"presentation\">\n <div class=\"go-confirm\" role=\"alertdialog\" aria-modal=\"true\" aria-label=\"\u0110\u1EB7t l\u1EA1i m\u1EB7c \u0111\u1ECBnh\" (click)=\"$event.stopPropagation()\">\n <h2 class=\"go-confirm__title\">\u0110\u1EB7t l\u1EA1i m\u1EB7c \u0111\u1ECBnh?</h2>\n <p class=\"go-confirm__body\">\n Checklist s\u1EBD tr\u1EDF l\u1EA1i nh\u01B0 danh s\u00E1ch m\u1EABu: b\u1ECF tick, xo\u00E1 ghi ch\u00FA, b\u1ECF \u0111\u00E1nh d\u1EA5u kh\u00F4ng c\u1EA7n, hi\u1EC7n l\u1EA1i m\u00F3n \u0111\u00E3 \u1EA9n v\u00E0 b\u1ECF m\u00F3n ch\u1EC9 th\u00EAm cho l\u1EA7n \u0111i n\u00E0y.\n Th\u1EE9 t\u1EF1 s\u1EAFp x\u1EBFp tay c\u0169ng \u0111\u01B0\u1EE3c \u0111\u1EB7t l\u1EA1i theo danh s\u00E1ch m\u1EABu. Danh s\u00E1ch m\u1EABu trong c\u00E0i \u0111\u1EB7t kh\u00F4ng thay \u0111\u1ED5i.\n </p>\n <div class=\"go-confirm__foot\">\n <button type=\"button\" class=\"go-btn go-btn--ghost\" (click)=\"cancelReset()\">Hu\u1EF7</button>\n <button type=\"button\" class=\"go-btn go-btn--primary\" (click)=\"confirmReset()\">\u0110\u1EB7t l\u1EA1i m\u1EB7c \u0111\u1ECBnh</button>\n </div>\n </div>\n </div>\n}\n", styles: ["@charset \"UTF-8\";:host{--bg: #f2f3f7;--surface: #ffffff;--border: #e2e5ee;--primary: #5b6cff;--primary-soft: #eef0ff;--orange: #ea580c;--orange-soft: #fff0e2;--green: #16a34a;--green-soft: #e4fbec;--amber: #d97706;--text: #0f172a;--muted: #64748b;--muted-2: #94a3b8;position:fixed;inset:0;z-index:200;background:var(--bg);display:flex;flex-direction:column;color:var(--text);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;animation:go-slide .22s cubic-bezier(.22,1,.36,1)}@keyframes go-slide{0%{transform:translate(8px);opacity:0}to{transform:translate(0);opacity:1}}.go-root{display:flex;flex-direction:column;flex:1;min-height:0}.go-header{flex-shrink:0;display:flex;align-items:center;gap:6px;padding:8px 10px;padding-top:calc(8px + env(safe-area-inset-top,0px));background:var(--surface);border-bottom:1px solid var(--border)}.go-back{width:32px;height:32px;border:none;background:transparent;border-radius:9px;color:var(--muted);font-size:15px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;-webkit-tap-highlight-color:transparent}.go-back:hover{background:var(--primary-soft);color:var(--primary)}.go-header__title{margin:0;font-size:16px;font-weight:700;line-height:1.15;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.go-header__spacer{flex:1}.go-header-menu{position:relative;flex-shrink:0}.go-header-menu__backdrop{position:fixed;inset:0;z-index:5;border:none;background:transparent;cursor:default;padding:0}.go-header-menu__panel{position:absolute;top:calc(100% + 4px);right:0;z-index:6;min-width:220px;padding:6px;background:var(--surface);border:1px solid var(--border);border-radius:12px}.go-header-menu__item{width:100%;display:flex;align-items:center;gap:8px;padding:10px;border:none;border-radius:8px;background:transparent;color:var(--text);font-size:13px;font-weight:600;text-align:left;cursor:pointer}.go-header-menu__item i{color:var(--muted);font-size:14px}.go-header-menu__item:hover{background:var(--primary-soft)}.go-icon-btn{width:32px;height:32px;border:none;background:transparent;border-radius:9px;color:var(--muted);font-size:15px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;-webkit-tap-highlight-color:transparent}.go-icon-btn:hover:not(:disabled){background:var(--primary-soft);color:var(--primary)}.go-icon-btn:disabled{opacity:.55;cursor:default}.go-icon-btn__spin{animation:go-refresh-spin .8s linear infinite}@keyframes go-refresh-spin{to{transform:rotate(360deg)}}.go-hero{flex-shrink:0;display:flex;align-items:center;gap:12px;margin:10px 12px;padding:14px;background:var(--orange-soft);border:1px solid var(--border);border-radius:16px}.go-hero--done{background:var(--green-soft)}.go-hero__copy{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}.go-hero__eyebrow{font-size:10px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--orange)}.go-hero--done .go-hero__eyebrow{color:var(--green)}.go-hero__title{font-size:17px;font-weight:800;letter-spacing:-.02em;line-height:1.15}.go-hero__sub{font-size:12px;color:var(--muted);line-height:1.35}.go-hero__ring{flex-shrink:0;width:56px;height:56px;border-radius:50%;border:4px solid var(--orange);background:var(--surface);display:grid;place-items:center;font-size:13px;font-weight:800;color:var(--orange)}.go-hero__ring--done{border-color:var(--green);color:var(--green)}.go-list-hint{margin:0 12px 6px;font-size:11px;color:var(--muted-2);line-height:1.35}.go-list-hint i{font-size:10px}.go-list{flex:1;min-height:0;overflow:auto;-webkit-overflow-scrolling:touch;padding:4px 12px calc(16px + env(safe-area-inset-bottom,0px));display:flex;flex-direction:column;gap:8px}.go-row{display:flex;align-items:flex-start;gap:6px;padding:8px 8px 8px 4px;background:var(--surface);border:1px solid var(--border);border-radius:14px}.go-row--not-needed{opacity:.62}.go-row--dragging{opacity:.45}.go-row--drag-over{border-color:var(--primary);background:var(--primary-soft)}.go-row__sort{flex-shrink:0;display:flex;flex-direction:column;align-items:center;gap:0;padding-top:2px}.go-sort-btn{width:26px;height:20px;padding:0;border:none;background:transparent;color:var(--muted-2);font-size:11px;cursor:pointer;border-radius:4px;-webkit-tap-highlight-color:transparent}.go-sort-btn:hover:not(:disabled){color:var(--primary);background:var(--primary-soft)}.go-sort-btn:disabled{opacity:.25;cursor:default}.go-row__grip{width:26px;height:26px;display:inline-flex;align-items:center;justify-content:center;color:var(--muted-2);font-size:12px;cursor:grab;touch-action:none;border-radius:6px}.go-row__grip:active{cursor:grabbing}.go-row__grip:hover{color:var(--muted);background:var(--bg)}.go-check{flex-shrink:0;width:30px;height:30px;margin-top:1px;padding:0;border:2px solid var(--border);border-radius:50%;background:var(--surface);color:#fff;font-size:14px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;position:relative;transition:border-color .15s,background .15s,transform .1s;-webkit-tap-highlight-color:transparent}.go-check:active{transform:scale(.9)}.go-check--none{border-color:var(--muted-2)}.go-check--partial{border-color:var(--amber);background:var(--orange-soft);color:var(--amber)}.go-check__half{font-size:13px;font-weight:800;line-height:1}.go-check--done{border-color:var(--primary);background:var(--primary)}.go-check--skipped{border-color:var(--muted-2);background:#f1f5f9;color:var(--muted);cursor:default;opacity:.95}.go-row__open{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px;padding:4px 2px;border:none;background:transparent;text-align:left;cursor:pointer;color:inherit;border-radius:8px;-webkit-tap-highlight-color:transparent}.go-row__open:active{background:var(--primary-soft)}.go-row__title-row{display:flex;flex-wrap:wrap;align-items:center;gap:6px}.go-row__badge{font-size:10px;font-weight:700;padding:2px 7px;border-radius:99px;background:var(--bg);color:var(--muted);border:1px solid var(--border)}.go-row__title{font-size:14px;font-weight:700;color:var(--text);word-break:break-word}.go-row__title--done{color:var(--muted);text-decoration:line-through}.go-row__title--skipped{color:var(--muted-2);text-decoration:line-through;font-weight:600}.go-row__desc{font-size:13px;color:var(--muted);word-break:break-word}.go-row__note{font-size:12px;color:var(--muted);line-height:1.35}.go-row__detail{flex-shrink:0;margin-top:4px;color:var(--muted);font-size:14px}.go-empty{margin:24px 12px;text-align:center;font-size:14px;color:var(--muted)}.go-add-wrap{flex-shrink:0;padding:8px 12px calc(16px + env(safe-area-inset-bottom,0px))}.go-add{width:100%;border:1px dashed var(--primary);background:var(--primary-soft);color:var(--primary);border-radius:14px;padding:12px;font-size:15px;font-weight:700;display:inline-flex;align-items:center;justify-content:center;gap:8px;cursor:pointer;-webkit-tap-highlight-color:transparent}.go-overlay{position:fixed;inset:0;z-index:420;background:#0f172a80;display:flex;align-items:center;justify-content:center;padding:16px;animation:go-fade .18s ease-out}@keyframes go-fade{0%{opacity:0}to{opacity:1}}.go-confirm{width:100%;max-width:360px;background:var(--surface);border-radius:16px;padding:18px;animation:go-sheet-in .2s cubic-bezier(.22,1,.36,1)}@keyframes go-sheet-in{0%{transform:translateY(12px);opacity:0}to{transform:translateY(0);opacity:1}}.go-confirm__title{margin:0 0 6px;font-size:17px;font-weight:700}.go-confirm__body{margin:0 0 16px;font-size:14px;color:var(--muted)}.go-confirm__foot{display:flex;justify-content:flex-end;gap:8px}.go-btn{border:none;border-radius:10px;padding:9px 16px;font-size:14px;font-weight:600;cursor:pointer;-webkit-tap-highlight-color:transparent}.go-btn--ghost{background:transparent;color:var(--muted)}.go-btn--primary{background:var(--primary);color:#fff}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: GoingOutEditDialogComponent, selector: "app-going-out-edit-dialog", inputs: ["mode", "heading", "showStatus", "showNote", "showNotNeeded", "canEditTitleDesc", "allowPartial", "initialTitle", "initialDesc", "initialNote", "initialStatus", "initialNotNeeded", "showRemoveFromTrip"], outputs: ["cancel", "save", "removeFromTrip"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
21714
22056
|
}
|
|
21715
22057
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: GoingOutChecklistComponent, decorators: [{
|
|
21716
22058
|
type: Component,
|
|
21717
|
-
args: [{ selector: 'app-going-out-checklist', standalone: true, imports: [CommonModule, GoingOutEditDialogComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section class=\"go-root\" role=\"dialog\" aria-modal=\"true\" [attr.aria-label]=\"title()\">\n <header class=\"go-header\">\n <button type=\"button\" class=\"go-back\" (click)=\"back.emit()\" aria-label=\"Quay l\u1EA1i\">\n <i class=\"pi pi-chevron-left\" aria-hidden=\"true\"></i>\n </button>\n <h1 class=\"go-header__title\">{{ title() }}</h1>\n <span class=\"go-header__spacer\"></span>\n <button\n type=\"button\"\n class=\"go-header-btn\"\n (click)=\"refresh()\"\n [disabled]=\"sheetLoading()\"\n title=\"L\u1EA5y checklist m\u1EDBi nh\u1EA5t t\u1EEB Google Sheet (\u0111\u1ED3ng b\u1ED9 v\u1EDBi ng\u01B0\u1EDDi kh\u00E1c)\"\n aria-label=\"\u0110\u1ED3ng b\u1ED9 t\u1EEB Google Sheet\"\n >\n <i class=\"pi pi-cloud-download\" [class.go-header-btn__spin]=\"sheetLoading()\" aria-hidden=\"true\"></i>\n <span>\u0110\u1ED3ng b\u1ED9</span>\n </button>\n <button\n type=\"button\"\n class=\"go-header-btn go-header-btn--muted\"\n (click)=\"askReset()\"\n title=\"\u0110\u1EB7t l\u1EA1i checklist: tick, ghi ch\u00FA, m\u00F3n \u0111\u00E3 \u1EA9n ho\u1EB7c th\u00EAm t\u1EA1m\"\n aria-label=\"\u0110\u1EB7t l\u1EA1i m\u1EB7c \u0111\u1ECBnh\"\n >\n <i class=\"pi pi-undo\" aria-hidden=\"true\"></i>\n <span>\u0110\u1EB7t l\u1EA1i m\u1EB7c \u0111\u1ECBnh</span>\n </button>\n <button\n type=\"button\"\n class=\"go-icon-btn\"\n (click)=\"manage.emit()\"\n title=\"Qu\u1EA3n l\u00FD danh s\u00E1ch m\u00F3n m\u1EABu\"\n aria-label=\"Qu\u1EA3n l\u00FD danh s\u00E1ch\"\n >\n <i class=\"pi pi-list\" aria-hidden=\"true\"></i>\n </button>\n </header>\n\n @if (allDone()) {\n <div class=\"go-ready\" role=\"status\">\n <i class=\"pi pi-check-circle\" aria-hidden=\"true\"></i>\n <span>S\u1EB5n s\u00E0ng \u2014 mang \u0111\u1ED3 \u0111i th\u00F4i</span>\n </div>\n }\n\n <div class=\"go-progress\">\n <div class=\"go-progress__row\">\n <span class=\"go-progress__count\">{{ doneCount() }}/{{ total() }}</span>\n <div class=\"go-progress__meta\">\n <span class=\"go-progress__state\" [class.go-progress__state--done]=\"allDone()\">\n {{ allDone() ? 'S\u1EB5n s\u00E0ng \u0111i r\u1ED3i!' : '\u0110ang chu\u1EA9n b\u1ECB\u2026' }}\n </span>\n <span class=\"go-progress__sub\">\n {{ readyCount() }} \u0111ang l\u00E0m \u00B7 {{ total() - readyCount() }} ch\u01B0a b\u1EAFt \u0111\u1EA7u\n @if (notNeededCount() > 0) {\n \u00B7 {{ notNeededCount() }} kh\u00F4ng c\u1EA7n\n }\n </span>\n </div>\n </div>\n <div class=\"go-bar\"><span class=\"go-bar__fill\" [style.width.%]=\"total() ? (doneCount() / total()) * 100 : 0\"></span></div>\n </div>\n\n <div class=\"go-list\">\n @for (item of items(); track item.id) {\n <div\n class=\"go-row\"\n [class.go-row--not-needed]=\"item.notNeeded\"\n (click)=\"cycleStatus(item)\"\n >\n <button\n type=\"button\"\n class=\"go-check\"\n [class.go-check--none]=\"item.status === 'none' && !item.notNeeded\"\n [class.go-check--partial]=\"item.status === 'partial'\"\n [class.go-check--done]=\"item.status === 'done'\"\n [class.go-check--skipped]=\"item.notNeeded\"\n (click)=\"cycleStatus(item); $event.stopPropagation()\"\n [attr.aria-label]=\"item.notNeeded ? 'Kh\u00F4ng c\u1EA7n l\u1EA7n n\u00E0y' : statusLabel(item.status)\"\n [attr.aria-pressed]=\"item.notNeeded || item.status !== 'none'\"\n [disabled]=\"item.notNeeded\"\n >\n @if (!item.notNeeded && item.status === 'done') {\n <i class=\"pi pi-check\" aria-hidden=\"true\"></i>\n }\n </button>\n\n <div class=\"go-row__body\" (click)=\"cycleStatus(item); $event.stopPropagation()\">\n <span\n class=\"go-row__title\"\n [class.go-row__title--done]=\"item.status === 'done'\"\n [class.go-row__title--skipped]=\"item.notNeeded\"\n >{{ item.title }}</span>\n @if (item.desc) {\n <span class=\"go-row__desc\">{{ item.desc }}</span>\n }\n @if (item.note) {\n <span class=\"go-row__note\">\n <i class=\"pi pi-file-edit\" aria-hidden=\"true\"></i>\n {{ item.note }}\n </span>\n }\n </div>\n\n <button\n type=\"button\"\n class=\"go-icon-btn go-row__skip\"\n [class.go-row__skip--on]=\"item.notNeeded\"\n (click)=\"toggleNotNeeded(item, $event)\"\n [title]=\"item.notNeeded ? 'C\u1EA7n mang l\u1EA1i l\u1EA7n n\u00E0y' : 'Kh\u00F4ng c\u1EA7n mang l\u1EA7n n\u00E0y'\"\n [attr.aria-label]=\"item.notNeeded ? 'C\u1EA7n mang l\u1EA1i l\u1EA7n n\u00E0y' : 'Kh\u00F4ng c\u1EA7n mang l\u1EA7n n\u00E0y'\"\n [attr.aria-pressed]=\"item.notNeeded\"\n >\n <i class=\"pi pi-ban\" aria-hidden=\"true\"></i>\n </button>\n\n <button\n type=\"button\"\n class=\"go-icon-btn go-row__edit\"\n (click)=\"openEdit(item); $event.stopPropagation()\"\n title=\"Ghi ch\u00FA / s\u1EEDa\"\n aria-label=\"Ghi ch\u00FA / s\u1EEDa\"\n >\n <i class=\"pi pi-pencil\" aria-hidden=\"true\"></i>\n </button>\n\n @if (allowEdit()) {\n <button\n type=\"button\"\n class=\"go-icon-btn go-row__danger\"\n (click)=\"removeItem(item, $event)\"\n title=\"B\u1ECF kh\u1ECFi checklist l\u1EA7n n\u00E0y\"\n aria-label=\"B\u1ECF kh\u1ECFi checklist l\u1EA7n n\u00E0y\"\n >\n <i class=\"pi pi-trash\" aria-hidden=\"true\"></i>\n </button>\n }\n </div>\n } @empty {\n <p class=\"go-empty\">Ch\u01B0a c\u00F3 m\u00F3n n\u00E0o.</p>\n }\n </div>\n\n @if (allowAdd()) {\n <div class=\"go-add-wrap\">\n <button type=\"button\" class=\"go-add\" (click)=\"openAdd()\">\n <i class=\"pi pi-plus\" aria-hidden=\"true\"></i>\n <span>Th\u00EAm m\u00F3n</span>\n </button>\n </div>\n }\n</section>\n\n@if (isDialogOpen()) {\n <app-going-out-edit-dialog\n [heading]=\"dialogHeading()\"\n [mode]=\"dialogMode()\"\n [showStatus]=\"true\"\n [showNote]=\"true\"\n [canEditTitleDesc]=\"dialogCanEditTitleDesc()\"\n [allowPartial]=\"allowPartial()\"\n [initialTitle]=\"editingItem()?.title ?? ''\"\n [initialDesc]=\"editingItem()?.desc ?? ''\"\n [initialNote]=\"editingItem()?.note ?? ''\"\n [initialStatus]=\"editingItem()?.status ?? 'none'\"\n (cancel)=\"closeDialog()\"\n (save)=\"onSaveEdit($event)\"\n />\n}\n\n@if (resetConfirm()) {\n <div class=\"go-overlay\" (click)=\"cancelReset()\" role=\"presentation\">\n <div class=\"go-confirm\" role=\"alertdialog\" aria-modal=\"true\" aria-label=\"\u0110\u1EB7t l\u1EA1i m\u1EB7c \u0111\u1ECBnh\" (click)=\"$event.stopPropagation()\">\n <h2 class=\"go-confirm__title\">\u0110\u1EB7t l\u1EA1i m\u1EB7c \u0111\u1ECBnh?</h2>\n <p class=\"go-confirm__body\">\n Checklist s\u1EBD tr\u1EDF l\u1EA1i nh\u01B0 danh s\u00E1ch m\u1EABu: b\u1ECF tick, xo\u00E1 ghi ch\u00FA, b\u1ECF \u0111\u00E1nh d\u1EA5u kh\u00F4ng c\u1EA7n, hi\u1EC7n l\u1EA1i m\u00F3n \u0111\u00E3 \u1EA9n v\u00E0 b\u1ECF m\u00F3n ch\u1EC9 th\u00EAm cho l\u1EA7n \u0111i n\u00E0y.\n Danh s\u00E1ch m\u1EABu trong c\u00E0i \u0111\u1EB7t kh\u00F4ng thay \u0111\u1ED5i.\n </p>\n <div class=\"go-confirm__foot\">\n <button type=\"button\" class=\"go-btn go-btn--ghost\" (click)=\"cancelReset()\">Hu\u1EF7</button>\n <button type=\"button\" class=\"go-btn go-btn--primary\" (click)=\"confirmReset()\">\u0110\u1EB7t l\u1EA1i m\u1EB7c \u0111\u1ECBnh</button>\n </div>\n </div>\n </div>\n}\n", styles: ["@charset \"UTF-8\";:host{--bg: #f6f7fb;--surface: #ffffff;--border: #e6e8ef;--primary: #5b6cff;--primary-soft: #eef0ff;--text: #0f172a;--muted: #475569;--muted-2: #94a3b8;position:fixed;inset:0;z-index:200;background:var(--bg);display:flex;flex-direction:column;color:var(--text);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;animation:go-slide .22s cubic-bezier(.22,1,.36,1)}@keyframes go-slide{0%{transform:translate(8px);opacity:0}to{transform:translate(0);opacity:1}}.go-root{display:flex;flex-direction:column;flex:1;min-height:0}.go-header{flex-shrink:0;display:flex;align-items:center;gap:6px;padding:8px 10px;padding-top:calc(8px + env(safe-area-inset-top,0px));background:var(--surface);border-bottom:1px solid var(--border)}.go-back{width:32px;height:32px;border:none;background:transparent;border-radius:9px;color:var(--muted);font-size:15px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;-webkit-tap-highlight-color:transparent}.go-back:hover{background:var(--primary-soft);color:var(--primary)}.go-header__title{margin:0;font-size:16px;font-weight:700;line-height:1.15;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.go-header__spacer{flex:1}.go-header-btn{flex-shrink:0;display:inline-flex;align-items:center;gap:4px;height:30px;padding:0 8px;border:1px solid var(--border);border-radius:8px;background:var(--surface);color:var(--primary);font-size:11px;font-weight:700;cursor:pointer;-webkit-tap-highlight-color:transparent}.go-header-btn i{font-size:12px}.go-header-btn:hover:not(:disabled){background:var(--primary-soft);border-color:color-mix(in srgb,var(--primary) 35%,var(--border))}.go-header-btn:disabled{opacity:.55;cursor:default}.go-header-btn--muted{color:var(--muted)}.go-header-btn--muted:hover:not(:disabled){background:#f1f5f9;border-color:var(--border);color:var(--text)}.go-header-btn__spin{animation:go-refresh-spin .8s linear infinite}.go-icon-btn{width:32px;height:32px;border:none;background:transparent;border-radius:9px;color:var(--muted);font-size:15px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;-webkit-tap-highlight-color:transparent}.go-icon-btn:hover:not(:disabled){background:var(--primary-soft);color:var(--primary)}.go-icon-btn:disabled{opacity:.55;cursor:default}.go-icon-btn__spin{animation:go-refresh-spin .8s linear infinite}@keyframes go-refresh-spin{to{transform:rotate(360deg)}}.go-ready{display:flex;align-items:center;gap:8px;margin:12px;padding:8px 14px;background:#e9f9ef;border:1px solid #b6e7c4;border-radius:12px;color:#15803d;font-size:13px;font-weight:600}.go-ready i{font-size:16px}.go-progress{margin:0 12px 10px;padding:12px 14px;background:var(--surface);border:1px solid var(--border);border-radius:14px}.go-progress__row{display:flex;align-items:center;gap:12px;margin-bottom:10px}.go-progress__count{font-size:22px;font-weight:800;color:var(--text);line-height:1}.go-progress__meta{display:flex;flex-direction:column;gap:2px}.go-progress__state{font-size:14px;font-weight:700}.go-progress__state--done{color:#16a34a}.go-progress__sub{font-size:12px;color:var(--muted)}.go-bar{height:6px;border-radius:999px;background:var(--border);overflow:hidden}.go-bar__fill{display:block;height:100%;background:var(--primary);border-radius:999px;transition:width .25s ease}.go-list{flex:1;min-height:0;overflow:auto;-webkit-overflow-scrolling:touch;padding:4px 12px calc(16px + env(safe-area-inset-bottom,0px));display:flex;flex-direction:column;gap:8px}.go-row{display:flex;align-items:flex-start;gap:10px;padding:12px;background:var(--surface);border:1px solid var(--border);border-radius:14px;cursor:pointer;-webkit-tap-highlight-color:transparent}.go-row:active{background:var(--primary-soft)}.go-row--not-needed{background:#f8fafc;border-color:#e2e8f0}.go-row--not-needed:active{background:#f1f5f9}.go-check{flex-shrink:0;width:30px;height:30px;margin-top:1px;padding:0;border:2px solid var(--border);border-radius:50%;background:var(--surface);color:#fff;font-size:14px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;position:relative;transition:border-color .15s,background .15s,transform .1s;-webkit-tap-highlight-color:transparent}.go-check:active{transform:scale(.9)}.go-check--none{border-color:var(--muted-2)}.go-check--partial{border-color:#f59e0b}.go-check--partial:before{content:\"\";position:absolute;inset:0;border-radius:50%;background:linear-gradient(to right,#f59e0b 50%,transparent 50%)}.go-check--partial i{position:relative;z-index:1}.go-check--done{border-color:var(--primary);background:var(--primary)}.go-check--skipped{border-color:var(--muted-2);background:#f1f5f9;color:var(--muted);cursor:default;opacity:.95}.go-row__body{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.go-row__title{font-size:15px;font-weight:700;color:var(--text);word-break:break-word}.go-row__title--done{color:var(--muted);text-decoration:line-through}.go-row__title--skipped{color:var(--muted-2);text-decoration:line-through;font-weight:600}.go-row__desc{font-size:13px;color:var(--muted);word-break:break-word}.go-row__note{display:flex;align-items:flex-start;gap:6px;margin-top:4px;font-size:12px;color:var(--primary);background:var(--primary-soft);border-radius:8px;padding:4px 8px}.go-row__note i{font-size:12px;margin-top:1px}.go-row__skip{flex-shrink:0;color:var(--muted-2)}.go-row__skip--on{color:#64748b;background:#e2e8f0}.go-row__skip:hover{background:#f1f5f9;color:var(--muted)}.go-row__skip--on:hover{background:#cbd5e1;color:var(--text)}.go-row__edit{flex-shrink:0}.go-row__danger{flex-shrink:0;color:var(--danger, #ef4444)}.go-row__danger:hover{background:#fee2e2;color:#ef4444}.go-empty{margin:24px 12px;text-align:center;font-size:14px;color:var(--muted)}.go-add-wrap{flex-shrink:0;padding:8px 12px calc(16px + env(safe-area-inset-bottom,0px))}.go-add{width:100%;border:1px dashed var(--primary);background:var(--primary-soft);color:var(--primary);border-radius:14px;padding:12px;font-size:15px;font-weight:700;display:inline-flex;align-items:center;justify-content:center;gap:8px;cursor:pointer;-webkit-tap-highlight-color:transparent}.go-overlay{position:fixed;inset:0;z-index:420;background:#0f172a80;display:flex;align-items:center;justify-content:center;padding:16px;animation:go-fade .18s ease-out}@keyframes go-fade{0%{opacity:0}to{opacity:1}}.go-confirm{width:100%;max-width:360px;background:var(--surface);border-radius:16px;padding:18px;animation:go-sheet-in .2s cubic-bezier(.22,1,.36,1)}@keyframes go-sheet-in{0%{transform:translateY(12px);opacity:0}to{transform:translateY(0);opacity:1}}.go-confirm__title{margin:0 0 6px;font-size:17px;font-weight:700}.go-confirm__body{margin:0 0 16px;font-size:14px;color:var(--muted)}.go-confirm__foot{display:flex;justify-content:flex-end;gap:8px}.go-btn{border:none;border-radius:10px;padding:9px 16px;font-size:14px;font-weight:600;cursor:pointer;-webkit-tap-highlight-color:transparent}.go-btn--ghost{background:transparent;color:var(--muted)}.go-btn--primary{background:var(--primary);color:#fff}\n"] }]
|
|
22059
|
+
args: [{ selector: 'app-going-out-checklist', standalone: true, imports: [CommonModule, GoingOutEditDialogComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section class=\"go-root\" role=\"dialog\" aria-modal=\"true\" [attr.aria-label]=\"title()\">\n <header class=\"go-header\">\n <button type=\"button\" class=\"go-back\" (click)=\"back.emit()\" aria-label=\"Quay l\u1EA1i\">\n <i class=\"pi pi-chevron-left\" aria-hidden=\"true\"></i>\n </button>\n <h1 class=\"go-header__title\">{{ title() }}</h1>\n <span class=\"go-header__spacer\"></span>\n <button\n type=\"button\"\n class=\"go-icon-btn\"\n (click)=\"refresh()\"\n [disabled]=\"sheetLoading()\"\n title=\"\u0110\u1ED3ng b\u1ED9 t\u1EEB Google Sheet\"\n aria-label=\"\u0110\u1ED3ng b\u1ED9 t\u1EEB Google Sheet\"\n >\n <i class=\"pi pi-cloud-download\" [class.go-icon-btn__spin]=\"sheetLoading()\" aria-hidden=\"true\"></i>\n </button>\n <div class=\"go-header-menu\">\n <button\n type=\"button\"\n class=\"go-icon-btn\"\n (click)=\"toggleHeaderMenu()\"\n aria-label=\"Th\u00EAm thao t\u00E1c\"\n aria-haspopup=\"menu\"\n [attr.aria-expanded]=\"headerMenuOpen()\"\n >\n <i class=\"pi pi-ellipsis-v\" aria-hidden=\"true\"></i>\n </button>\n @if (headerMenuOpen()) {\n <div class=\"go-header-menu__panel\" role=\"menu\">\n <button type=\"button\" role=\"menuitem\" class=\"go-header-menu__item\" (click)=\"onManageFromMenu()\">\n <i class=\"pi pi-list\" aria-hidden=\"true\"></i>\n Qu\u1EA3n l\u00FD danh s\u00E1ch m\u1EABu\n </button>\n <button type=\"button\" role=\"menuitem\" class=\"go-header-menu__item\" (click)=\"onResetFromMenu()\">\n <i class=\"pi pi-undo\" aria-hidden=\"true\"></i>\n \u0110\u1EB7t l\u1EA1i m\u1EB7c \u0111\u1ECBnh\n </button>\n </div>\n }\n </div>\n </header>\n\n @if (headerMenuOpen()) {\n <button type=\"button\" class=\"go-header-menu__backdrop\" aria-label=\"\u0110\u00F3ng menu\" (click)=\"closeHeaderMenu()\"></button>\n }\n\n <div\n class=\"go-hero\"\n [class.go-hero--done]=\"allDone()\"\n role=\"status\"\n aria-live=\"polite\"\n >\n <div class=\"go-hero__copy\">\n <span class=\"go-hero__eyebrow\">Ra ngo\u00E0i</span>\n <span class=\"go-hero__title\">{{ heroTitle() }}</span>\n <span class=\"go-hero__sub\">\n {{ packedCount() }}/{{ activeTripCount() }} m\u00F3n s\u1EB5n s\u00E0ng\n @if (notNeededCount() > 0) {\n \u00B7 {{ notNeededCount() }} kh\u00F4ng mang\n }\n </span>\n </div>\n <div\n class=\"go-hero__ring\"\n [class.go-hero__ring--done]=\"allDone()\"\n [attr.aria-label]=\"'Ti\u1EBFn \u0111\u1ED9 ' + progressPercent() + ' ph\u1EA7n tr\u0103m'\"\n >\n {{ progressPercent() }}%\n </div>\n </div>\n\n <p class=\"go-list-hint\">Ch\u1EA1m d\u00F2ng \u0111\u1EC3 tick \u00B7 K\u00E9o <i class=\"pi pi-bars\" aria-hidden=\"true\"></i> ho\u1EB7c \u2191\u2193 \u0111\u1EC3 s\u1EAFp x\u1EBFp (ch\u1EC9 tr\u00EAn m\u00E1y)</p>\n\n <div class=\"go-list\">\n @for (item of items(); track item.id; let i = $index; let last = $last) {\n <div\n class=\"go-row\"\n [class.go-row--not-needed]=\"item.notNeeded\"\n [class.go-row--dragging]=\"draggingId() === item.id\"\n [class.go-row--drag-over]=\"dragOverId() === item.id\"\n (dragover)=\"onDragOver($event, item)\"\n (drop)=\"onDrop($event, item)\"\n >\n <div class=\"go-row__sort\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"go-sort-btn\"\n [disabled]=\"i === 0\"\n (click)=\"moveItem(i, -1, $event)\"\n aria-label=\"\u0110\u01B0a m\u00F3n l\u00EAn\"\n >\n <i class=\"pi pi-angle-up\" aria-hidden=\"true\"></i>\n </button>\n <span\n class=\"go-row__grip\"\n draggable=\"true\"\n title=\"K\u00E9o \u0111\u1EC3 \u0111\u1ED5i th\u1EE9 t\u1EF1\"\n aria-label=\"K\u00E9o \u0111\u1EC3 \u0111\u1ED5i th\u1EE9 t\u1EF1\"\n (dragstart)=\"onDragStart($event, item)\"\n (dragend)=\"onDragEnd()\"\n >\n <i class=\"pi pi-bars\" aria-hidden=\"true\"></i>\n </span>\n <button\n type=\"button\"\n class=\"go-sort-btn\"\n [disabled]=\"last\"\n (click)=\"moveItem(i, 1, $event)\"\n aria-label=\"\u0110\u01B0a m\u00F3n xu\u1ED1ng\"\n >\n <i class=\"pi pi-angle-down\" aria-hidden=\"true\"></i>\n </button>\n </div>\n\n <button\n type=\"button\"\n class=\"go-check\"\n [class.go-check--none]=\"item.status === 'none' && !item.notNeeded\"\n [class.go-check--partial]=\"item.status === 'partial' && !item.notNeeded\"\n [class.go-check--done]=\"item.status === 'done' && !item.notNeeded\"\n [class.go-check--skipped]=\"item.notNeeded\"\n (click)=\"cycleStatus(item); $event.stopPropagation()\"\n [attr.aria-label]=\"item.notNeeded ? 'Kh\u00F4ng mang l\u1EA7n n\u00E0y' : statusLabel(item.status)\"\n [disabled]=\"item.notNeeded\"\n >\n @if (!item.notNeeded && item.status === 'done') {\n <i class=\"pi pi-check\" aria-hidden=\"true\"></i>\n }\n @if (!item.notNeeded && item.status === 'partial') {\n <span class=\"go-check__half\" aria-hidden=\"true\">\u00BD</span>\n }\n </button>\n\n <button\n type=\"button\"\n class=\"go-row__open\"\n (click)=\"onMainTap(item, $event)\"\n [attr.aria-label]=\"item.notNeeded ? item.title : '\u0110\u1ED5i tr\u1EA1ng th\u00E1i ' + item.title\"\n >\n <span class=\"go-row__title-row\">\n <span\n class=\"go-row__title\"\n [class.go-row__title--done]=\"item.status === 'done' && !item.notNeeded\"\n [class.go-row__title--skipped]=\"item.notNeeded\"\n >{{ item.title }}</span>\n <span class=\"go-row__badge\">{{ itemStatusBadge(item) }}</span>\n </span>\n @if (item.desc) {\n <span class=\"go-row__desc\">{{ item.desc }}</span>\n }\n @if (item.note) {\n <span class=\"go-row__note\">{{ item.note }}</span>\n }\n </button>\n\n <button\n type=\"button\"\n class=\"go-icon-btn go-row__detail\"\n (click)=\"openDetail(item, $event)\"\n title=\"Xem chi ti\u1EBFt\"\n aria-label=\"Xem chi ti\u1EBFt\"\n >\n <i class=\"pi pi-angle-right\" aria-hidden=\"true\"></i>\n </button>\n </div>\n } @empty {\n <p class=\"go-empty\">Ch\u01B0a c\u00F3 m\u00F3n n\u00E0o.</p>\n }\n </div>\n\n @if (allowAdd()) {\n <div class=\"go-add-wrap\">\n <button type=\"button\" class=\"go-add\" (click)=\"openAdd()\">\n <i class=\"pi pi-plus\" aria-hidden=\"true\"></i>\n <span>Th\u00EAm m\u00F3n cho chuy\u1EBFn n\u00E0y</span>\n </button>\n </div>\n }\n</section>\n\n@if (isDialogOpen()) {\n <app-going-out-edit-dialog\n [heading]=\"dialogHeading()\"\n [mode]=\"dialogMode()\"\n [showStatus]=\"true\"\n [showNote]=\"true\"\n [showNotNeeded]=\"true\"\n [canEditTitleDesc]=\"dialogCanEditTitleDesc()\"\n [allowPartial]=\"allowPartial()\"\n [showRemoveFromTrip]=\"dialogShowRemove()\"\n [initialTitle]=\"editingItem()?.title ?? ''\"\n [initialDesc]=\"editingItem()?.desc ?? ''\"\n [initialNote]=\"editingItem()?.note ?? ''\"\n [initialStatus]=\"editingItem()?.status ?? 'none'\"\n [initialNotNeeded]=\"editingItem()?.notNeeded ?? false\"\n (cancel)=\"closeDialog()\"\n (save)=\"onSaveEdit($event)\"\n (removeFromTrip)=\"removeFromDialog()\"\n />\n}\n\n@if (resetConfirm()) {\n <div class=\"go-overlay\" (click)=\"cancelReset()\" role=\"presentation\">\n <div class=\"go-confirm\" role=\"alertdialog\" aria-modal=\"true\" aria-label=\"\u0110\u1EB7t l\u1EA1i m\u1EB7c \u0111\u1ECBnh\" (click)=\"$event.stopPropagation()\">\n <h2 class=\"go-confirm__title\">\u0110\u1EB7t l\u1EA1i m\u1EB7c \u0111\u1ECBnh?</h2>\n <p class=\"go-confirm__body\">\n Checklist s\u1EBD tr\u1EDF l\u1EA1i nh\u01B0 danh s\u00E1ch m\u1EABu: b\u1ECF tick, xo\u00E1 ghi ch\u00FA, b\u1ECF \u0111\u00E1nh d\u1EA5u kh\u00F4ng c\u1EA7n, hi\u1EC7n l\u1EA1i m\u00F3n \u0111\u00E3 \u1EA9n v\u00E0 b\u1ECF m\u00F3n ch\u1EC9 th\u00EAm cho l\u1EA7n \u0111i n\u00E0y.\n Th\u1EE9 t\u1EF1 s\u1EAFp x\u1EBFp tay c\u0169ng \u0111\u01B0\u1EE3c \u0111\u1EB7t l\u1EA1i theo danh s\u00E1ch m\u1EABu. Danh s\u00E1ch m\u1EABu trong c\u00E0i \u0111\u1EB7t kh\u00F4ng thay \u0111\u1ED5i.\n </p>\n <div class=\"go-confirm__foot\">\n <button type=\"button\" class=\"go-btn go-btn--ghost\" (click)=\"cancelReset()\">Hu\u1EF7</button>\n <button type=\"button\" class=\"go-btn go-btn--primary\" (click)=\"confirmReset()\">\u0110\u1EB7t l\u1EA1i m\u1EB7c \u0111\u1ECBnh</button>\n </div>\n </div>\n </div>\n}\n", styles: ["@charset \"UTF-8\";:host{--bg: #f2f3f7;--surface: #ffffff;--border: #e2e5ee;--primary: #5b6cff;--primary-soft: #eef0ff;--orange: #ea580c;--orange-soft: #fff0e2;--green: #16a34a;--green-soft: #e4fbec;--amber: #d97706;--text: #0f172a;--muted: #64748b;--muted-2: #94a3b8;position:fixed;inset:0;z-index:200;background:var(--bg);display:flex;flex-direction:column;color:var(--text);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;animation:go-slide .22s cubic-bezier(.22,1,.36,1)}@keyframes go-slide{0%{transform:translate(8px);opacity:0}to{transform:translate(0);opacity:1}}.go-root{display:flex;flex-direction:column;flex:1;min-height:0}.go-header{flex-shrink:0;display:flex;align-items:center;gap:6px;padding:8px 10px;padding-top:calc(8px + env(safe-area-inset-top,0px));background:var(--surface);border-bottom:1px solid var(--border)}.go-back{width:32px;height:32px;border:none;background:transparent;border-radius:9px;color:var(--muted);font-size:15px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;-webkit-tap-highlight-color:transparent}.go-back:hover{background:var(--primary-soft);color:var(--primary)}.go-header__title{margin:0;font-size:16px;font-weight:700;line-height:1.15;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.go-header__spacer{flex:1}.go-header-menu{position:relative;flex-shrink:0}.go-header-menu__backdrop{position:fixed;inset:0;z-index:5;border:none;background:transparent;cursor:default;padding:0}.go-header-menu__panel{position:absolute;top:calc(100% + 4px);right:0;z-index:6;min-width:220px;padding:6px;background:var(--surface);border:1px solid var(--border);border-radius:12px}.go-header-menu__item{width:100%;display:flex;align-items:center;gap:8px;padding:10px;border:none;border-radius:8px;background:transparent;color:var(--text);font-size:13px;font-weight:600;text-align:left;cursor:pointer}.go-header-menu__item i{color:var(--muted);font-size:14px}.go-header-menu__item:hover{background:var(--primary-soft)}.go-icon-btn{width:32px;height:32px;border:none;background:transparent;border-radius:9px;color:var(--muted);font-size:15px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;-webkit-tap-highlight-color:transparent}.go-icon-btn:hover:not(:disabled){background:var(--primary-soft);color:var(--primary)}.go-icon-btn:disabled{opacity:.55;cursor:default}.go-icon-btn__spin{animation:go-refresh-spin .8s linear infinite}@keyframes go-refresh-spin{to{transform:rotate(360deg)}}.go-hero{flex-shrink:0;display:flex;align-items:center;gap:12px;margin:10px 12px;padding:14px;background:var(--orange-soft);border:1px solid var(--border);border-radius:16px}.go-hero--done{background:var(--green-soft)}.go-hero__copy{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}.go-hero__eyebrow{font-size:10px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--orange)}.go-hero--done .go-hero__eyebrow{color:var(--green)}.go-hero__title{font-size:17px;font-weight:800;letter-spacing:-.02em;line-height:1.15}.go-hero__sub{font-size:12px;color:var(--muted);line-height:1.35}.go-hero__ring{flex-shrink:0;width:56px;height:56px;border-radius:50%;border:4px solid var(--orange);background:var(--surface);display:grid;place-items:center;font-size:13px;font-weight:800;color:var(--orange)}.go-hero__ring--done{border-color:var(--green);color:var(--green)}.go-list-hint{margin:0 12px 6px;font-size:11px;color:var(--muted-2);line-height:1.35}.go-list-hint i{font-size:10px}.go-list{flex:1;min-height:0;overflow:auto;-webkit-overflow-scrolling:touch;padding:4px 12px calc(16px + env(safe-area-inset-bottom,0px));display:flex;flex-direction:column;gap:8px}.go-row{display:flex;align-items:flex-start;gap:6px;padding:8px 8px 8px 4px;background:var(--surface);border:1px solid var(--border);border-radius:14px}.go-row--not-needed{opacity:.62}.go-row--dragging{opacity:.45}.go-row--drag-over{border-color:var(--primary);background:var(--primary-soft)}.go-row__sort{flex-shrink:0;display:flex;flex-direction:column;align-items:center;gap:0;padding-top:2px}.go-sort-btn{width:26px;height:20px;padding:0;border:none;background:transparent;color:var(--muted-2);font-size:11px;cursor:pointer;border-radius:4px;-webkit-tap-highlight-color:transparent}.go-sort-btn:hover:not(:disabled){color:var(--primary);background:var(--primary-soft)}.go-sort-btn:disabled{opacity:.25;cursor:default}.go-row__grip{width:26px;height:26px;display:inline-flex;align-items:center;justify-content:center;color:var(--muted-2);font-size:12px;cursor:grab;touch-action:none;border-radius:6px}.go-row__grip:active{cursor:grabbing}.go-row__grip:hover{color:var(--muted);background:var(--bg)}.go-check{flex-shrink:0;width:30px;height:30px;margin-top:1px;padding:0;border:2px solid var(--border);border-radius:50%;background:var(--surface);color:#fff;font-size:14px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;position:relative;transition:border-color .15s,background .15s,transform .1s;-webkit-tap-highlight-color:transparent}.go-check:active{transform:scale(.9)}.go-check--none{border-color:var(--muted-2)}.go-check--partial{border-color:var(--amber);background:var(--orange-soft);color:var(--amber)}.go-check__half{font-size:13px;font-weight:800;line-height:1}.go-check--done{border-color:var(--primary);background:var(--primary)}.go-check--skipped{border-color:var(--muted-2);background:#f1f5f9;color:var(--muted);cursor:default;opacity:.95}.go-row__open{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px;padding:4px 2px;border:none;background:transparent;text-align:left;cursor:pointer;color:inherit;border-radius:8px;-webkit-tap-highlight-color:transparent}.go-row__open:active{background:var(--primary-soft)}.go-row__title-row{display:flex;flex-wrap:wrap;align-items:center;gap:6px}.go-row__badge{font-size:10px;font-weight:700;padding:2px 7px;border-radius:99px;background:var(--bg);color:var(--muted);border:1px solid var(--border)}.go-row__title{font-size:14px;font-weight:700;color:var(--text);word-break:break-word}.go-row__title--done{color:var(--muted);text-decoration:line-through}.go-row__title--skipped{color:var(--muted-2);text-decoration:line-through;font-weight:600}.go-row__desc{font-size:13px;color:var(--muted);word-break:break-word}.go-row__note{font-size:12px;color:var(--muted);line-height:1.35}.go-row__detail{flex-shrink:0;margin-top:4px;color:var(--muted);font-size:14px}.go-empty{margin:24px 12px;text-align:center;font-size:14px;color:var(--muted)}.go-add-wrap{flex-shrink:0;padding:8px 12px calc(16px + env(safe-area-inset-bottom,0px))}.go-add{width:100%;border:1px dashed var(--primary);background:var(--primary-soft);color:var(--primary);border-radius:14px;padding:12px;font-size:15px;font-weight:700;display:inline-flex;align-items:center;justify-content:center;gap:8px;cursor:pointer;-webkit-tap-highlight-color:transparent}.go-overlay{position:fixed;inset:0;z-index:420;background:#0f172a80;display:flex;align-items:center;justify-content:center;padding:16px;animation:go-fade .18s ease-out}@keyframes go-fade{0%{opacity:0}to{opacity:1}}.go-confirm{width:100%;max-width:360px;background:var(--surface);border-radius:16px;padding:18px;animation:go-sheet-in .2s cubic-bezier(.22,1,.36,1)}@keyframes go-sheet-in{0%{transform:translateY(12px);opacity:0}to{transform:translateY(0);opacity:1}}.go-confirm__title{margin:0 0 6px;font-size:17px;font-weight:700}.go-confirm__body{margin:0 0 16px;font-size:14px;color:var(--muted)}.go-confirm__foot{display:flex;justify-content:flex-end;gap:8px}.go-btn{border:none;border-radius:10px;padding:9px 16px;font-size:14px;font-weight:600;cursor:pointer;-webkit-tap-highlight-color:transparent}.go-btn--ghost{background:transparent;color:var(--muted)}.go-btn--primary{background:var(--primary);color:#fff}\n"] }]
|
|
21718
22060
|
}], ctorParameters: () => [], propDecorators: { babyName: [{ type: i0.Input, args: [{ isSignal: true, alias: "babyName", required: false }] }], back: [{ type: i0.Output, args: ["back"] }], manage: [{ type: i0.Output, args: ["manage"] }] } });
|
|
21719
22061
|
|
|
21720
22062
|
class GoingOutListComponent {
|
|
@@ -21772,7 +22114,7 @@ class GoingOutListComponent {
|
|
|
21772
22114
|
this.svc.setAllowPartial(v);
|
|
21773
22115
|
}
|
|
21774
22116
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: GoingOutListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
21775
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: GoingOutListComponent, isStandalone: true, selector: "app-going-out-list", outputs: { back: "back" }, ngImport: i0, template: "<section class=\"go-root\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Danh s\u00E1ch m\u00F3n\">\n <header class=\"go-header\">\n <button type=\"button\" class=\"go-back\" (click)=\"back.emit()\" aria-label=\"Quay l\u1EA1i\">\n <i class=\"pi pi-chevron-left\" aria-hidden=\"true\"></i>\n </button>\n <h1 class=\"go-header__title\">C\u00E0i \u0111\u1EB7t ra ngo\u00E0i</h1>\n <span class=\"go-header__spacer\"></span>\n </header>\n\n <!-- Quy\u1EC1n -->\n <div class=\"go-perms\">\n <label class=\"go-toggle\">\n <span class=\"go-toggle__text\">\n <span class=\"go-toggle__title\">Cho ph\u00E9p s\u1EEDa & xo\u00E1 m\u00F3n</span>\n <span class=\"go-toggle__sub\">\u00C1p d\u1EE5ng cho trang checklist (kh\u00F4ng kho\u00E1 trang n\u00E0y)</span>\n </span>\n <input\n type=\"checkbox\"\n role=\"switch\"\n [checked]=\"allowEdit()\"\n (change)=\"setAllowEdit($any($event.target).checked)\"\n />\n </label>\n <label class=\"go-toggle\">\n <span class=\"go-toggle__text\">\n <span class=\"go-toggle__title\">Cho ph\u00E9p th\u00EAm m\u00F3n</span>\n <span class=\"go-toggle__sub\">Hi\u1EC7n n\u00FAt \"Th\u00EAm m\u00F3n\" \u1EDF trang checklist</span>\n </span>\n <input\n type=\"checkbox\"\n role=\"switch\"\n [checked]=\"allowAdd()\"\n (change)=\"setAllowAdd($any($event.target).checked)\"\n />\n </label>\n <label class=\"go-toggle\">\n <span class=\"go-toggle__text\">\n <span class=\"go-toggle__title\">Cho ph\u00E9p tr\u1EA1ng th\u00E1i \"1 ph\u1EA7n\"</span>\n <span class=\"go-toggle__sub\">T\u1EAFt \u0111\u1EC3 checkbox ch\u1EC9 c\u00F2n ch\u01B0a / xong</span>\n </span>\n <input\n type=\"checkbox\"\n role=\"switch\"\n [checked]=\"allowPartial()\"\n (change)=\"setAllowPartial($any($event.target).checked)\"\n />\n </label>\n </div>\n\n <div class=\"go-list\">\n @for (item of items(); track item.id) {\n <div class=\"go-row\" (click)=\"openEdit(item)\">\n <div class=\"go-row__body\">\n <span class=\"go-row__title\">{{ item.title }}</span>\n @if (item.desc) {\n <span class=\"go-row__desc\">{{ item.desc }}</span>\n }\n </div>\n <button\n type=\"button\"\n class=\"go-icon-btn go-row__danger\"\n (click)=\"removeItem(item, $event)\"\n title=\"Xo\u00E1 m\u00F3n\"\n aria-label=\"Xo\u00E1 m\u00F3n\"\n >\n <i class=\"pi pi-trash\" aria-hidden=\"true\"></i>\n </button>\n </div>\n } @empty {\n <p class=\"go-empty\">Ch\u01B0a c\u00F3 m\u00F3n n\u00E0o. B\u1EA5m \u201CTh\u00EAm m\u00F3n\u201D \u0111\u1EC3 b\u1EAFt \u0111\u1EA7u.</p>\n }\n </div>\n\n <div class=\"go-add-wrap\">\n <button type=\"button\" class=\"go-add\" (click)=\"openAdd()\">\n <i class=\"pi pi-plus\" aria-hidden=\"true\"></i>\n <span>Th\u00EAm m\u00F3n</span>\n </button>\n </div>\n</section>\n\n@if (isDialogOpen()) {\n <app-going-out-edit-dialog\n [heading]=\"dialogHeading()\"\n [mode]=\"dialogMode()\"\n [showStatus]=\"false\"\n [showNote]=\"false\"\n [canEditTitleDesc]=\"true\"\n [initialTitle]=\"draftTitle()\"\n [initialDesc]=\"draftDesc()\"\n (cancel)=\"closeDialog()\"\n (save)=\"onSave($event)\"\n />\n}\n", styles: [":host{--bg: #f6f7fb;--surface: #ffffff;--border: #e6e8ef;--primary: #5b6cff;--primary-soft: #eef0ff;--text: #0f172a;--muted: #475569;--muted-2: #94a3b8;--danger: #ef4444;--danger-soft: #fee2e2;position:fixed;inset:0;z-index:210;background:var(--bg);display:flex;flex-direction:column;color:var(--text);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;animation:go-slide .22s cubic-bezier(.22,1,.36,1)}@keyframes go-slide{0%{transform:translate(8px);opacity:0}to{transform:translate(0);opacity:1}}.go-root{display:flex;flex-direction:column;flex:1;min-height:0}.go-header{flex-shrink:0;display:flex;align-items:center;gap:6px;padding:8px 10px;padding-top:calc(8px + env(safe-area-inset-top,0px));background:var(--surface);border-bottom:1px solid var(--border)}.go-back{width:32px;height:32px;border:none;background:transparent;border-radius:9px;color:var(--muted);font-size:15px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;-webkit-tap-highlight-color:transparent}.go-back:hover{background:var(--primary-soft);color:var(--primary)}.go-header__title{margin:0;font-size:16px;font-weight:700}.go-header__spacer{flex:1}.go-perms{display:flex;flex-direction:column;gap:8px;padding:12px}.go-toggle{display:flex;align-items:center;gap:12px;padding:12px 14px;background:var(--surface);border:1px solid var(--border);border-radius:14px}.go-toggle__text{flex:1;display:flex;flex-direction:column;gap:2px}.go-toggle__title{font-size:14px;font-weight:700;color:var(--text)}.go-toggle__sub{font-size:12px;color:var(--muted)}.go-toggle input[type=checkbox]{width:40px;height:24px;flex-shrink:0;accent-color:var(--primary)}.go-list{flex:1;min-height:0;overflow:auto;-webkit-overflow-scrolling:touch;padding:0 12px 12px;display:flex;flex-direction:column;gap:8px}.go-row{display:flex;align-items:center;gap:10px;padding:12px 14px;background:var(--surface);border:1px solid var(--border);border-radius:14px;cursor:pointer;-webkit-tap-highlight-color:transparent}.go-row:active{background:var(--primary-soft)}.go-row__body{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.go-row__title{font-size:15px;font-weight:700;color:var(--text);word-break:break-word}.go-row__desc{font-size:13px;color:var(--muted);word-break:break-word}.go-icon-btn{width:32px;height:32px;border:none;background:transparent;border-radius:9px;color:var(--muted);font-size:15px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;-webkit-tap-highlight-color:transparent}.go-icon-btn:hover{background:var(--primary-soft);color:var(--primary)}.go-row__danger{color:var(--danger)}.go-row__danger:hover{background:var(--danger-soft);color:var(--danger)}.go-empty{margin:24px 12px;text-align:center;font-size:14px;color:var(--muted)}.go-add-wrap{flex-shrink:0;padding:8px 12px calc(16px + env(safe-area-inset-bottom,0px))}.go-add{width:100%;border:1px dashed var(--primary);background:var(--primary-soft);color:var(--primary);border-radius:14px;padding:12px;font-size:15px;font-weight:700;display:inline-flex;align-items:center;justify-content:center;gap:8px;cursor:pointer;-webkit-tap-highlight-color:transparent}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: GoingOutEditDialogComponent, selector: "app-going-out-edit-dialog", inputs: ["mode", "heading", "showStatus", "showNote", "canEditTitleDesc", "allowPartial", "initialTitle", "initialDesc", "initialNote", "initialStatus"], outputs: ["cancel", "save"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
22117
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: GoingOutListComponent, isStandalone: true, selector: "app-going-out-list", outputs: { back: "back" }, ngImport: i0, template: "<section class=\"go-root\" role=\"dialog\" aria-modal=\"true\" aria-label=\"Danh s\u00E1ch m\u00F3n\">\n <header class=\"go-header\">\n <button type=\"button\" class=\"go-back\" (click)=\"back.emit()\" aria-label=\"Quay l\u1EA1i\">\n <i class=\"pi pi-chevron-left\" aria-hidden=\"true\"></i>\n </button>\n <h1 class=\"go-header__title\">C\u00E0i \u0111\u1EB7t ra ngo\u00E0i</h1>\n <span class=\"go-header__spacer\"></span>\n </header>\n\n <!-- Quy\u1EC1n -->\n <div class=\"go-perms\">\n <label class=\"go-toggle\">\n <span class=\"go-toggle__text\">\n <span class=\"go-toggle__title\">Cho ph\u00E9p s\u1EEDa & xo\u00E1 m\u00F3n</span>\n <span class=\"go-toggle__sub\">\u00C1p d\u1EE5ng cho trang checklist (kh\u00F4ng kho\u00E1 trang n\u00E0y)</span>\n </span>\n <input\n type=\"checkbox\"\n role=\"switch\"\n [checked]=\"allowEdit()\"\n (change)=\"setAllowEdit($any($event.target).checked)\"\n />\n </label>\n <label class=\"go-toggle\">\n <span class=\"go-toggle__text\">\n <span class=\"go-toggle__title\">Cho ph\u00E9p th\u00EAm m\u00F3n</span>\n <span class=\"go-toggle__sub\">Hi\u1EC7n n\u00FAt \"Th\u00EAm m\u00F3n\" \u1EDF trang checklist</span>\n </span>\n <input\n type=\"checkbox\"\n role=\"switch\"\n [checked]=\"allowAdd()\"\n (change)=\"setAllowAdd($any($event.target).checked)\"\n />\n </label>\n <label class=\"go-toggle\">\n <span class=\"go-toggle__text\">\n <span class=\"go-toggle__title\">Cho ph\u00E9p tr\u1EA1ng th\u00E1i \"1 ph\u1EA7n\"</span>\n <span class=\"go-toggle__sub\">T\u1EAFt \u0111\u1EC3 checkbox ch\u1EC9 c\u00F2n ch\u01B0a / xong</span>\n </span>\n <input\n type=\"checkbox\"\n role=\"switch\"\n [checked]=\"allowPartial()\"\n (change)=\"setAllowPartial($any($event.target).checked)\"\n />\n </label>\n </div>\n\n <div class=\"go-list\">\n @for (item of items(); track item.id) {\n <div class=\"go-row\" (click)=\"openEdit(item)\">\n <div class=\"go-row__body\">\n <span class=\"go-row__title\">{{ item.title }}</span>\n @if (item.desc) {\n <span class=\"go-row__desc\">{{ item.desc }}</span>\n }\n </div>\n <button\n type=\"button\"\n class=\"go-icon-btn go-row__danger\"\n (click)=\"removeItem(item, $event)\"\n title=\"Xo\u00E1 m\u00F3n\"\n aria-label=\"Xo\u00E1 m\u00F3n\"\n >\n <i class=\"pi pi-trash\" aria-hidden=\"true\"></i>\n </button>\n </div>\n } @empty {\n <p class=\"go-empty\">Ch\u01B0a c\u00F3 m\u00F3n n\u00E0o. B\u1EA5m \u201CTh\u00EAm m\u00F3n\u201D \u0111\u1EC3 b\u1EAFt \u0111\u1EA7u.</p>\n }\n </div>\n\n <div class=\"go-add-wrap\">\n <button type=\"button\" class=\"go-add\" (click)=\"openAdd()\">\n <i class=\"pi pi-plus\" aria-hidden=\"true\"></i>\n <span>Th\u00EAm m\u00F3n</span>\n </button>\n </div>\n</section>\n\n@if (isDialogOpen()) {\n <app-going-out-edit-dialog\n [heading]=\"dialogHeading()\"\n [mode]=\"dialogMode()\"\n [showStatus]=\"false\"\n [showNote]=\"false\"\n [canEditTitleDesc]=\"true\"\n [initialTitle]=\"draftTitle()\"\n [initialDesc]=\"draftDesc()\"\n (cancel)=\"closeDialog()\"\n (save)=\"onSave($event)\"\n />\n}\n", styles: [":host{--bg: #f6f7fb;--surface: #ffffff;--border: #e6e8ef;--primary: #5b6cff;--primary-soft: #eef0ff;--text: #0f172a;--muted: #475569;--muted-2: #94a3b8;--danger: #ef4444;--danger-soft: #fee2e2;position:fixed;inset:0;z-index:210;background:var(--bg);display:flex;flex-direction:column;color:var(--text);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;animation:go-slide .22s cubic-bezier(.22,1,.36,1)}@keyframes go-slide{0%{transform:translate(8px);opacity:0}to{transform:translate(0);opacity:1}}.go-root{display:flex;flex-direction:column;flex:1;min-height:0}.go-header{flex-shrink:0;display:flex;align-items:center;gap:6px;padding:8px 10px;padding-top:calc(8px + env(safe-area-inset-top,0px));background:var(--surface);border-bottom:1px solid var(--border)}.go-back{width:32px;height:32px;border:none;background:transparent;border-radius:9px;color:var(--muted);font-size:15px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;-webkit-tap-highlight-color:transparent}.go-back:hover{background:var(--primary-soft);color:var(--primary)}.go-header__title{margin:0;font-size:16px;font-weight:700}.go-header__spacer{flex:1}.go-perms{display:flex;flex-direction:column;gap:8px;padding:12px}.go-toggle{display:flex;align-items:center;gap:12px;padding:12px 14px;background:var(--surface);border:1px solid var(--border);border-radius:14px}.go-toggle__text{flex:1;display:flex;flex-direction:column;gap:2px}.go-toggle__title{font-size:14px;font-weight:700;color:var(--text)}.go-toggle__sub{font-size:12px;color:var(--muted)}.go-toggle input[type=checkbox]{width:40px;height:24px;flex-shrink:0;accent-color:var(--primary)}.go-list{flex:1;min-height:0;overflow:auto;-webkit-overflow-scrolling:touch;padding:0 12px 12px;display:flex;flex-direction:column;gap:8px}.go-row{display:flex;align-items:center;gap:10px;padding:12px 14px;background:var(--surface);border:1px solid var(--border);border-radius:14px;cursor:pointer;-webkit-tap-highlight-color:transparent}.go-row:active{background:var(--primary-soft)}.go-row__body{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.go-row__title{font-size:15px;font-weight:700;color:var(--text);word-break:break-word}.go-row__desc{font-size:13px;color:var(--muted);word-break:break-word}.go-icon-btn{width:32px;height:32px;border:none;background:transparent;border-radius:9px;color:var(--muted);font-size:15px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;-webkit-tap-highlight-color:transparent}.go-icon-btn:hover{background:var(--primary-soft);color:var(--primary)}.go-row__danger{color:var(--danger)}.go-row__danger:hover{background:var(--danger-soft);color:var(--danger)}.go-empty{margin:24px 12px;text-align:center;font-size:14px;color:var(--muted)}.go-add-wrap{flex-shrink:0;padding:8px 12px calc(16px + env(safe-area-inset-bottom,0px))}.go-add{width:100%;border:1px dashed var(--primary);background:var(--primary-soft);color:var(--primary);border-radius:14px;padding:12px;font-size:15px;font-weight:700;display:inline-flex;align-items:center;justify-content:center;gap:8px;cursor:pointer;-webkit-tap-highlight-color:transparent}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: GoingOutEditDialogComponent, selector: "app-going-out-edit-dialog", inputs: ["mode", "heading", "showStatus", "showNote", "showNotNeeded", "canEditTitleDesc", "allowPartial", "initialTitle", "initialDesc", "initialNote", "initialStatus", "initialNotNeeded", "showRemoveFromTrip"], outputs: ["cancel", "save", "removeFromTrip"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
21776
22118
|
}
|
|
21777
22119
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: GoingOutListComponent, decorators: [{
|
|
21778
22120
|
type: Component,
|
|
@@ -21871,7 +22213,6 @@ class PreparingPurchaseService {
|
|
|
21871
22213
|
this.cache = null;
|
|
21872
22214
|
this.loadInFlight = null;
|
|
21873
22215
|
this.bootstrap.invalidatePreparing();
|
|
21874
|
-
this.bootstrap.invalidate('goingOutItems');
|
|
21875
22216
|
}
|
|
21876
22217
|
getProducts() {
|
|
21877
22218
|
return this.loadLists().pipe(map((data) => [...data.products].sort((a, b) => a.name.localeCompare(b.name, 'vi'))));
|
|
@@ -21998,26 +22339,18 @@ class PreparingPurchaseService {
|
|
|
21998
22339
|
}
|
|
21999
22340
|
fetchListsFromSheet() {
|
|
22000
22341
|
return this.bootstrap.loadPreparing().pipe(map(() => {
|
|
22001
|
-
const productRows = this.bootstrap.getRows('preparingProducts') ??
|
|
22002
|
-
this.bootstrap.getRows('goingOutItems') ??
|
|
22003
|
-
[];
|
|
22004
|
-
const legacyProductRows = this.bootstrap.getRows('preparingProductsLegacy') ?? [];
|
|
22342
|
+
const productRows = this.bootstrap.getRows('preparingProducts') ?? [];
|
|
22005
22343
|
const requestRows = this.bootstrap.getRows('preparingRequests') ?? [];
|
|
22006
22344
|
const commentRows = this.bootstrap.getRows('preparingComments') ?? [];
|
|
22007
|
-
return this.buildLists(this.
|
|
22345
|
+
return this.buildLists(this.parsePreparingProductRows(productRows), this.parseRequestRows(requestRows), this.parseCommentRows(commentRows));
|
|
22008
22346
|
}), catchError(() => forkJoin({
|
|
22009
22347
|
products: this.readProducts().pipe(catchError(() => of([]))),
|
|
22010
22348
|
requests: this.readRequests().pipe(catchError(() => of([]))),
|
|
22011
22349
|
comments: this.readComments().pipe(catchError(() => of([]))),
|
|
22012
22350
|
}).pipe(map(({ products, requests, comments }) => this.buildLists(products, requests, comments)))));
|
|
22013
22351
|
}
|
|
22014
|
-
|
|
22015
|
-
|
|
22016
|
-
.map((row, idx) => this.parseCatalogProductRow(row, idx + 2))
|
|
22017
|
-
.filter((p) => !!p);
|
|
22018
|
-
}
|
|
22019
|
-
/** Danh mục cũ trên `Preparing!A:C`: ID | name | Description (hoặc name | Description). */
|
|
22020
|
-
parseLegacyPreparingProductRows(rows) {
|
|
22352
|
+
/** Danh mục trên `Preparing!A:C`: ID | name | Description. */
|
|
22353
|
+
parsePreparingProductRows(rows) {
|
|
22021
22354
|
return rows
|
|
22022
22355
|
.map((row, idx) => {
|
|
22023
22356
|
const colA = (row[0] || '').trim();
|
|
@@ -22042,46 +22375,6 @@ class PreparingPurchaseService {
|
|
|
22042
22375
|
})
|
|
22043
22376
|
.filter((p) => !!p);
|
|
22044
22377
|
}
|
|
22045
|
-
parseCatalogProductRow(row, rowIndex) {
|
|
22046
|
-
const id = (row[0] || '').trim();
|
|
22047
|
-
const deletedRaw = (row[5] || '').trim();
|
|
22048
|
-
if (!id || this.isTruthy(deletedRaw))
|
|
22049
|
-
return null;
|
|
22050
|
-
let name = (row[1] || '').trim();
|
|
22051
|
-
let description = (row[2] ?? '').toString().trim();
|
|
22052
|
-
const createdBy = (row[3] || '').trim();
|
|
22053
|
-
const createdAt = (row[4] || '').trim();
|
|
22054
|
-
// Sau khi thêm cột ID: tên có thể còn nằm ở cột Description (J).
|
|
22055
|
-
if (!name && description) {
|
|
22056
|
-
name = description;
|
|
22057
|
-
description = '';
|
|
22058
|
-
}
|
|
22059
|
-
// Title trùng UUID nhưng Description vẫn là tên hiển thị.
|
|
22060
|
-
if (name && name === id && this.looksLikeTechnicalId(id) && description) {
|
|
22061
|
-
name = description;
|
|
22062
|
-
description = '';
|
|
22063
|
-
}
|
|
22064
|
-
if (!name)
|
|
22065
|
-
return null;
|
|
22066
|
-
return {
|
|
22067
|
-
id,
|
|
22068
|
-
name,
|
|
22069
|
-
description,
|
|
22070
|
-
createdBy: createdBy.toLowerCase(),
|
|
22071
|
-
createdAt,
|
|
22072
|
-
rowIndex,
|
|
22073
|
-
};
|
|
22074
|
-
}
|
|
22075
|
-
mergeProducts(primary, legacy) {
|
|
22076
|
-
const map = new Map();
|
|
22077
|
-
for (const product of legacy) {
|
|
22078
|
-
map.set(product.id, product);
|
|
22079
|
-
}
|
|
22080
|
-
for (const product of primary) {
|
|
22081
|
-
map.set(product.id, product);
|
|
22082
|
-
}
|
|
22083
|
-
return [...map.values()];
|
|
22084
|
-
}
|
|
22085
22378
|
parseRequestRows(rows) {
|
|
22086
22379
|
return rows
|
|
22087
22380
|
.map((row, idx) => this.mapRequestRow(row, idx + 2))
|
|
@@ -22233,22 +22526,13 @@ class PreparingPurchaseService {
|
|
|
22233
22526
|
this.cache.purchased = [purchased, ...this.cache.purchased].sort(this.sortByPurchasedDesc);
|
|
22234
22527
|
}
|
|
22235
22528
|
readProducts() {
|
|
22236
|
-
return
|
|
22237
|
-
|
|
22238
|
-
|
|
22239
|
-
|
|
22240
|
-
|
|
22241
|
-
|
|
22242
|
-
|
|
22243
|
-
.pipe(catchError(() => of([]))),
|
|
22244
|
-
legacy: this.googleSheets
|
|
22245
|
-
.getSheetValues({
|
|
22246
|
-
spreadsheetId: this.SHEET_ID,
|
|
22247
|
-
sheetName: this.SHEET_NAME,
|
|
22248
|
-
range: 'A2:C',
|
|
22249
|
-
})
|
|
22250
|
-
.pipe(catchError(() => of([]))),
|
|
22251
|
-
}).pipe(map(({ catalog, legacy }) => this.mergeProducts(this.parseProductRows(catalog), this.parseLegacyPreparingProductRows(legacy))));
|
|
22529
|
+
return this.googleSheets
|
|
22530
|
+
.getSheetValues({
|
|
22531
|
+
spreadsheetId: this.SHEET_ID,
|
|
22532
|
+
sheetName: this.SHEET_NAME,
|
|
22533
|
+
range: 'A2:C',
|
|
22534
|
+
})
|
|
22535
|
+
.pipe(catchError(() => of([])), map((rows) => this.parsePreparingProductRows(rows)));
|
|
22252
22536
|
}
|
|
22253
22537
|
readRequests() {
|
|
22254
22538
|
return this.googleSheets
|
|
@@ -23068,16 +23352,22 @@ class PreparingPurchaseComponent {
|
|
|
23068
23352
|
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
23069
23353
|
return `${now.getFullYear()}-${month}`;
|
|
23070
23354
|
}
|
|
23071
|
-
|
|
23072
|
-
|
|
23073
|
-
if (
|
|
23074
|
-
this.
|
|
23075
|
-
|
|
23355
|
+
/** @param force When true (refresh), bypass cache and refetch from Sheet. */
|
|
23356
|
+
load(force = false) {
|
|
23357
|
+
if (!force) {
|
|
23358
|
+
const cached = this.svc.getCachedLists();
|
|
23359
|
+
if (cached) {
|
|
23360
|
+
this.applyLists(cached);
|
|
23361
|
+
return;
|
|
23362
|
+
}
|
|
23363
|
+
}
|
|
23364
|
+
else {
|
|
23365
|
+
this.svc.invalidateCache();
|
|
23076
23366
|
}
|
|
23077
23367
|
this.loading.set(true);
|
|
23078
23368
|
this.errorMsg.set('');
|
|
23079
23369
|
this.svc
|
|
23080
|
-
.loadLists()
|
|
23370
|
+
.loadLists(force)
|
|
23081
23371
|
.pipe(takeUntilDestroyed(this.destroyRef), catchError$1((err) => {
|
|
23082
23372
|
this.errorMsg.set(err?.message || 'Không tải được danh sách');
|
|
23083
23373
|
return of({
|
|
@@ -23449,7 +23739,7 @@ class PreparingPurchaseComponent {
|
|
|
23449
23739
|
this.successMsg.set('');
|
|
23450
23740
|
}
|
|
23451
23741
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: PreparingPurchaseComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
23452
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: PreparingPurchaseComponent, isStandalone: true, selector: "app-preparing-purchase", inputs: { user: { classPropertyName: "user", publicName: "user", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { back: "back", pendingCountChange: "pendingCountChange" }, host: { listeners: { "document:click": "onDocumentClick()" } }, ngImport: i0, template: "<section class=\"pp-root\" role=\"dialog\" aria-modal=\"true\" aria-label=\"C\u1EA7n mua s\u1EAFm\">\n <header class=\"pp-header\">\n <button type=\"button\" class=\"pp-back\" (click)=\"back.emit()\" aria-label=\"Quay l\u1EA1i\">\n <i class=\"pi pi-chevron-left\" aria-hidden=\"true\"></i>\n </button>\n <h1 class=\"pp-header__title\">C\u1EA7n mua s\u1EAFm</h1>\n <span class=\"pp-header__spacer\"></span>\n <button\n type=\"button\"\n class=\"pp-icon-btn\"\n (click)=\"load()\"\n [disabled]=\"loading()\"\n title=\"L\u00E0m m\u1EDBi danh s\u00E1ch\"\n aria-label=\"L\u00E0m m\u1EDBi danh s\u00E1ch\"\n >\n <i class=\"pi pi-refresh\" [class.pp-icon-btn__spin]=\"loading()\" aria-hidden=\"true\"></i>\n </button>\n </header>\n\n @if (errorMsg()) {\n <div class=\"pp-banner pp-banner--error\" role=\"alert\">\n <span>{{ errorMsg() }}</span>\n <button type=\"button\" class=\"pp-banner__close\" (click)=\"clearMessages()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\n </button>\n </div>\n }\n\n @if (successMsg()) {\n <div class=\"pp-banner pp-banner--success\" role=\"status\">\n <span>{{ successMsg() }}</span>\n <button type=\"button\" class=\"pp-banner__close\" (click)=\"clearMessages()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\n </button>\n </div>\n }\n\n <div class=\"pp-overview\" aria-label=\"T\u1ED5ng quan\">\n <div class=\"pp-overview__cell\">\n <span class=\"pp-overview__value pp-overview__value--amber\">{{ pendingItems().length }}</span>\n <span class=\"pp-overview__label\">Ch\u01B0a mua</span>\n </div>\n <div class=\"pp-overview__cell\">\n <span class=\"pp-overview__value pp-overview__value--danger\">{{ highPriorityCount() }}</span>\n <span class=\"pp-overview__label\">\u01AFu ti\u00EAn cao</span>\n </div>\n <div class=\"pp-overview__cell\">\n <span class=\"pp-overview__value pp-overview__value--danger\">{{ overdueCount() }}</span>\n <span class=\"pp-overview__label\">Qu\u00E1 h\u1EA1n</span>\n </div>\n <div class=\"pp-overview__cell\">\n <span class=\"pp-overview__value pp-overview__value--success\">{{ purchasedThisMonthCount() }}</span>\n <span class=\"pp-overview__label\">\u0110\u00E3 mua th\u00E1ng</span>\n </div>\n </div>\n\n @if (pendingItems().length > 0) {\n <div class=\"pp-priority-bar\" aria-hidden=\"true\">\n <div class=\"pp-priority-bar__track\">\n @for (segment of prioritySegments(); track segment.priority) {\n <span\n class=\"pp-priority-bar__segment\"\n [class.pp-priority-bar__segment--high]=\"segment.priority === 'high'\"\n [class.pp-priority-bar__segment--medium]=\"segment.priority === 'medium'\"\n [class.pp-priority-bar__segment--low]=\"segment.priority === 'low'\"\n [style.width.%]=\"segment.percent\"\n ></span>\n }\n </div>\n <div class=\"pp-priority-bar__legend\">\n <span>\u2191 Cao {{ priorityCounts().high }}</span>\n <span>\u2014 TB {{ priorityCounts().medium }}</span>\n <span>\u2193 Th\u1EA5p {{ priorityCounts().low }}</span>\n </div>\n </div>\n }\n\n <div class=\"pp-tabs\" role=\"tablist\" aria-label=\"Tr\u1EA1ng th\u00E1i mua\">\n <button\n type=\"button\"\n class=\"pp-tabs__btn\"\n role=\"tab\"\n [class.pp-tabs__btn--active]=\"activeTab() === 'pending'\"\n [attr.aria-selected]=\"activeTab() === 'pending'\"\n (click)=\"setTab('pending')\"\n >\n C\u1EA7n mua <span class=\"pp-tabs__count\">{{ pendingItems().length }}</span>\n </button>\n <button\n type=\"button\"\n class=\"pp-tabs__btn\"\n role=\"tab\"\n [class.pp-tabs__btn--active]=\"activeTab() === 'purchased'\"\n [attr.aria-selected]=\"activeTab() === 'purchased'\"\n (click)=\"setTab('purchased')\"\n >\n \u0110\u00E3 mua <span class=\"pp-tabs__count\">{{ purchasedItems().length }}</span>\n </button>\n </div>\n\n @if (activeTab() === 'pending') {\n <div class=\"pp-filters\" role=\"group\" aria-label=\"L\u1ECDc theo \u01B0u ti\u00EAn\">\n @for (chip of priorityFilterOptions; track chip.id) {\n <button\n type=\"button\"\n class=\"pp-filter-chip\"\n [class.pp-filter-chip--active]=\"priorityFilter() === chip.id\"\n (click)=\"setPriorityFilter(chip.id)\"\n >\n {{ chip.label }}\n </button>\n }\n </div>\n }\n\n <div class=\"pp-list\">\n @if (activeTab() === 'pending') {\n @if (priorityFilter() === 'all') {\n @for (group of pendingPriorityGroups(); track group.priority) {\n @if (group.items.length > 0) {\n <p\n class=\"pp-group-label\"\n [class.pp-group-label--high]=\"group.priority === 'high'\"\n [class.pp-group-label--medium]=\"group.priority === 'medium'\"\n [class.pp-group-label--low]=\"group.priority === 'low'\"\n >\n {{ priorityGroupHeading(group.priority) }}\n </p>\n @for (item of group.items; track itemKey(item)) {\n <ng-container *ngTemplateOutlet=\"pendingRow; context: { $implicit: item }\" />\n }\n }\n }\n } @else {\n <p\n class=\"pp-group-label\"\n [class.pp-group-label--high]=\"priorityFilter() === 'high'\"\n [class.pp-group-label--medium]=\"priorityFilter() === 'medium'\"\n [class.pp-group-label--low]=\"priorityFilter() === 'low'\"\n >\n {{ filteredPriorityHeading() }}\n </p>\n @for (item of filteredPendingItems(); track itemKey(item)) {\n <ng-container *ngTemplateOutlet=\"pendingRow; context: { $implicit: item }\" />\n }\n }\n\n @if (filteredPendingItems().length === 0 && !loading()) {\n <p class=\"pp-empty\">Kh\u00F4ng c\u00F2n m\u00F3n n\u00E0o c\u1EA7n mua.</p>\n }\n } @else {\n @for (item of purchasedItems(); track itemKey(item)) {\n <ng-container *ngTemplateOutlet=\"purchasedRow; context: { $implicit: item }\" />\n } @empty {\n @if (!loading()) {\n <p class=\"pp-empty\">Ch\u01B0a c\u00F3 m\u00F3n n\u00E0o \u0111\u00E3 mua.</p>\n }\n }\n }\n\n @if (loading() && pendingItems().length === 0 && purchasedItems().length === 0) {\n <p class=\"pp-empty\">\u0110ang t\u1EA3i\u2026</p>\n }\n </div>\n\n <div class=\"pp-add-wrap\">\n <button type=\"button\" class=\"pp-add\" (click)=\"openAdd()\" [disabled]=\"saving()\">\n <i class=\"pi pi-plus\" aria-hidden=\"true\"></i>\n <span>Th\u00EAm m\u00F3n</span>\n </button>\n </div>\n</section>\n\n<ng-template #pendingRow let-item>\n <article\n class=\"pp-row pp-row--pending pp-row--clickable\"\n [class.pp-row--menu-open]=\"rowMenuKey() === itemKey(item)\"\n [class.pp-row--high]=\"item.priority === 'high'\"\n [class.pp-row--medium]=\"item.priority === 'medium'\"\n [class.pp-row--low]=\"item.priority === 'low'\"\n (click)=\"openPendingDetailItem(item)\"\n role=\"button\"\n tabindex=\"0\"\n (keydown.enter)=\"openPendingDetailItem(item)\"\n (keydown.space)=\"$event.preventDefault(); openPendingDetailItem(item)\"\n [attr.aria-label]=\"'Xem chi ti\u1EBFt ' + item.itemName\"\n >\n <span\n class=\"pp-priority\"\n [class.pp-priority--high]=\"item.priority === 'high'\"\n [class.pp-priority--medium]=\"item.priority === 'medium'\"\n [class.pp-priority--low]=\"item.priority === 'low'\"\n [attr.title]=\"'\u01AFu ti\u00EAn ' + priorityLabel(item.priority)\"\n >\n <i class=\"fa-solid\" [ngClass]=\"priorityIcon(item.priority)\" aria-hidden=\"true\"></i>\n </span>\n\n <div class=\"pp-row__body\">\n <div class=\"pp-row__top\">\n <h2 class=\"pp-row__title\">{{ item.itemName }}</h2>\n @if (item.quantity) {\n <span class=\"pp-row__qty\">{{ item.quantity }}</span>\n }\n </div>\n @if (rowSubtitle(item); as subtitle) {\n <p class=\"pp-row__desc\">{{ subtitle }}</p>\n }\n <p class=\"pp-row__meta\">\n Th\u00EAm {{ formatDate(item.createdDate) }}\n @if (item.createdBy) {\n \u00B7 {{ item.createdBy }}\n }\n <span class=\"pp-row__comments\" [attr.title]=\"commentCountForRequest(item.requestId) + ' b\u00ECnh lu\u1EADn'\">\n \u00B7 <i class=\"pi pi-comment\" aria-hidden=\"true\"></i>\n {{ commentCountForRequest(item.requestId) }}\n </span>\n </p>\n </div>\n\n <div class=\"pp-row__actions-col\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"pp-row__confirm\"\n (click)=\"openPurchaseConfirm(item)\"\n [disabled]=\"saving()\"\n title=\"\u0110\u00E1nh d\u1EA5u \u0111\u00E3 mua\"\n aria-label=\"\u0110\u00E1nh d\u1EA5u \u0111\u00E3 mua\"\n >\n <i class=\"pi pi-check\" aria-hidden=\"true\"></i>\n </button>\n <div class=\"pp-row__menu-wrap\">\n <button\n type=\"button\"\n class=\"pp-icon-btn pp-icon-btn--menu\"\n (click)=\"toggleRowMenu(item, $event)\"\n title=\"Tu\u1EF3 ch\u1ECDn\"\n aria-label=\"Tu\u1EF3 ch\u1ECDn\"\n aria-haspopup=\"menu\"\n [attr.aria-expanded]=\"rowMenuKey() === itemKey(item)\"\n >\n <i class=\"pi pi-ellipsis-v\" aria-hidden=\"true\"></i>\n </button>\n @if (rowMenuKey() === itemKey(item)) {\n <div class=\"pp-row-menu\" role=\"menu\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"pp-row-menu__item\"\n role=\"menuitem\"\n (click)=\"openPendingDetail(item, $event)\"\n >\n Xem chi ti\u1EBFt\n </button>\n <button\n type=\"button\"\n class=\"pp-row-menu__item\"\n role=\"menuitem\"\n (click)=\"openEdit(item); rowMenuKey.set(null)\"\n >\n S\u1EEDa\n </button>\n <button\n type=\"button\"\n class=\"pp-row-menu__item pp-row-menu__item--danger\"\n role=\"menuitem\"\n (click)=\"askDelete(item, $event)\"\n >\n Xo\u00E1\n </button>\n </div>\n }\n </div>\n </div>\n </article>\n</ng-template>\n\n<ng-template #purchasedRow let-item>\n <article\n class=\"pp-row pp-row--purchased pp-row--clickable\"\n [class.pp-row--menu-open]=\"rowMenuKey() === itemKey(item)\"\n (click)=\"openPurchaseDetail(item, $event)\"\n role=\"button\"\n tabindex=\"0\"\n (keydown.enter)=\"openPurchaseDetail(item, $event)\"\n (keydown.space)=\"$event.preventDefault(); openPurchaseDetailItem(item)\"\n [attr.aria-label]=\"'Xem l\u1ECBch s\u1EED mua ' + item.itemName\"\n >\n <span\n class=\"pp-priority pp-priority--muted\"\n [class.pp-priority--high]=\"item.priority === 'high'\"\n [class.pp-priority--medium]=\"item.priority === 'medium'\"\n [class.pp-priority--low]=\"item.priority === 'low'\"\n [attr.title]=\"'\u01AFu ti\u00EAn ' + priorityLabel(item.priority)\"\n >\n <i class=\"fa-solid\" [ngClass]=\"priorityIcon(item.priority)\" aria-hidden=\"true\"></i>\n </span>\n\n <div class=\"pp-row__body\">\n <div class=\"pp-row__top\">\n <h2 class=\"pp-row__title\">{{ item.itemName }}</h2>\n @if (item.quantity) {\n <span class=\"pp-row__qty\">{{ item.quantity }}</span>\n }\n </div>\n <p class=\"pp-row__desc\">\n @if (item.description) {\n {{ item.description }}\n } @else if (item.purchasedDate) {\n Mua {{ formatDate(item.purchasedDate) }}\n }\n </p>\n <p class=\"pp-row__meta\">\n @if (item.purchasedDate) {\n \u0110\u00E3 mua {{ formatDate(item.purchasedDate) }}\n }\n \u00B7 {{ purchaseCountForProduct(item.productId) }} l\u1EA7n\n <span class=\"pp-row__comments\" [attr.title]=\"commentCountForRequest(item.requestId) + ' b\u00ECnh lu\u1EADn'\">\n \u00B7 <i class=\"pi pi-comment\" aria-hidden=\"true\"></i>\n {{ commentCountForRequest(item.requestId) }}\n </span>\n </p>\n </div>\n\n <div class=\"pp-row__menu-wrap\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"pp-icon-btn pp-icon-btn--menu\"\n (click)=\"toggleRowMenu(item, $event)\"\n title=\"Tu\u1EF3 ch\u1ECDn\"\n aria-label=\"Tu\u1EF3 ch\u1ECDn\"\n aria-haspopup=\"menu\"\n [attr.aria-expanded]=\"rowMenuKey() === itemKey(item)\"\n >\n <i class=\"pi pi-ellipsis-v\" aria-hidden=\"true\"></i>\n </button>\n @if (rowMenuKey() === itemKey(item)) {\n <div class=\"pp-row-menu\" role=\"menu\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"pp-row-menu__item\"\n role=\"menuitem\"\n (click)=\"openPurchaseDetail(item, $event)\"\n >\n Xem chi ti\u1EBFt\n <span class=\"pp-row-menu__badge\">{{ purchaseCountForProduct(item.productId) }}</span>\n </button>\n <button\n type=\"button\"\n class=\"pp-row-menu__item\"\n role=\"menuitem\"\n (click)=\"duplicateFromPurchased(item, $event)\"\n >\n Mua l\u1EA1i\n </button>\n </div>\n }\n </div>\n </article>\n</ng-template>\n\n@if (isDialogOpen()) {\n <app-preparing-purchase-dialog\n [heading]=\"dialogHeading()\"\n [mode]=\"dialogMode()\"\n [products]=\"products()\"\n [initialProductId]=\"dialogProductId()\"\n [initialProductName]=\"dialogProductName()\"\n [initialProductDescription]=\"dialogProductDescription()\"\n [initialAmount]=\"dialogAmount()\"\n [initialNote]=\"dialogNote()\"\n [initialAssignee]=\"dialogAssignee()\"\n [initialDueDate]=\"dialogDueDate()\"\n [initialPriority]=\"dialogPriority()\"\n [saving]=\"saving()\"\n (cancel)=\"closeDialog()\"\n (save)=\"onSave($event)\"\n />\n}\n\n@if (purchaseConfirm()) {\n <app-preparing-purchase-confirm-dialog\n [itemName]=\"purchaseConfirm()?.itemName ?? ''\"\n [quantity]=\"purchaseConfirm()?.quantity ?? ''\"\n [saving]=\"saving()\"\n (cancel)=\"cancelPurchaseConfirm()\"\n (confirm)=\"onConfirmPurchase($event)\"\n />\n}\n\n@if (pendingDetailItem(); as detail) {\n <div class=\"pp-overlay pp-overlay--detail\" (click)=\"closePendingDetail()\" role=\"presentation\">\n <div\n class=\"pp-detail\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"detail.itemName\"\n (click)=\"$event.stopPropagation()\"\n >\n <header class=\"pp-detail__hero\">\n <div class=\"pp-detail__hero-top\">\n <span\n class=\"pp-detail__priority\"\n [class.pp-detail__priority--high]=\"detail.priority === 'high'\"\n [class.pp-detail__priority--medium]=\"detail.priority === 'medium'\"\n [class.pp-detail__priority--low]=\"detail.priority === 'low'\"\n >\n <i class=\"fa-solid\" [ngClass]=\"priorityIcon(detail.priority)\" aria-hidden=\"true\"></i>\n {{ priorityLabel(detail.priority) }}\n </span>\n <button type=\"button\" class=\"pp-icon-btn pp-icon-btn--soft\" (click)=\"closePendingDetail()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"fa-solid fa-xmark\" aria-hidden=\"true\"></i>\n </button>\n </div>\n <h2 class=\"pp-detail__name\">{{ detail.itemName }}</h2>\n <p class=\"pp-detail__meta\">\n <span>\n <i class=\"fa-solid fa-user\" aria-hidden=\"true\"></i>\n {{ detail.createdBy || 'guest' }}\n </span>\n <span>\n <i class=\"fa-solid fa-clock\" aria-hidden=\"true\"></i>\n {{ formatDateTime(detail.createdDate) }}\n </span>\n </p>\n </header>\n\n <div class=\"pp-detail__body\">\n <section class=\"pp-detail-card\" aria-label=\"Y\u00EAu c\u1EA7u mua\">\n <h3 class=\"pp-detail-card__title\">Y\u00EAu c\u1EA7u mua</h3>\n <dl class=\"pp-detail-kv\">\n <div class=\"pp-detail-kv__row\">\n <dt>S\u1ED1 l\u01B0\u1EE3ng</dt>\n <dd>{{ detail.quantity || '\u2014' }}</dd>\n </div>\n <div class=\"pp-detail-kv__row\">\n <dt>Ng\u01B0\u1EDDi ph\u1EE5 tr\u00E1ch</dt>\n <dd>{{ itemAssignee(detail) || '\u2014' }}</dd>\n </div>\n <div class=\"pp-detail-kv__row\">\n <dt>Th\u1EDDi h\u1EA1n mua</dt>\n <dd [class.pp-detail-kv__value--overdue]=\"isItemOverdue(detail)\">\n {{ itemDueDate(detail) || '\u2014' }}\n </dd>\n </div>\n </dl>\n </section>\n\n @if (detail.description?.trim()) {\n <section class=\"pp-detail-card\" aria-label=\"M\u00F4 t\u1EA3 s\u1EA3n ph\u1EA9m\">\n <h3 class=\"pp-detail-card__title\">M\u00F4 t\u1EA3</h3>\n <p class=\"pp-detail-card__text\">{{ detail.description }}</p>\n </section>\n }\n\n @if (itemNoteText(detail); as noteText) {\n <section class=\"pp-detail-card\" aria-label=\"Ghi ch\u00FA\">\n <h3 class=\"pp-detail-card__title\">Ghi ch\u00FA</h3>\n <p class=\"pp-detail-card__text\">{{ noteText }}</p>\n </section>\n }\n\n <section class=\"pp-comments\" aria-label=\"B\u00ECnh lu\u1EADn\">\n <div class=\"pp-comments__head\">\n <h3 class=\"pp-comments__title\">B\u00ECnh lu\u1EADn</h3>\n <span class=\"pp-comments__count\">{{ detailComments().length }}</span>\n </div>\n\n <ul class=\"pp-comments__list\">\n @for (comment of detailComments(); track comment.id) {\n <li class=\"pp-comment\">\n <div class=\"pp-comment__head\">\n <span class=\"pp-comment__author\">{{ comment.createdBy || 'guest' }}</span>\n <span class=\"pp-comment__date\">{{ formatDateTime(comment.createdDate) }}</span>\n </div>\n <p class=\"pp-comment__text\">\n @for (part of commentSegments(comment.text); track $index) {\n @if (part.type === 'link') {\n <a\n class=\"pp-comment__link\"\n [href]=\"part.href\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n (click)=\"$event.stopPropagation()\"\n >{{ part.value }}</a>\n } @else {\n {{ part.value }}\n }\n }\n </p>\n </li>\n } @empty {\n <li class=\"pp-comments__empty\">Ch\u01B0a c\u00F3 b\u00ECnh lu\u1EADn \u2014 th\u00EAm ghi ch\u00FA cho m\u1ECDi ng\u01B0\u1EDDi c\u00F9ng xem.</li>\n }\n </ul>\n\n <div class=\"pp-comments__composer\">\n <textarea\n class=\"pp-comments__input\"\n rows=\"2\"\n [value]=\"commentDraft()\"\n (input)=\"onCommentDraft($any($event.target).value)\"\n [disabled]=\"commentSaving()\"\n placeholder=\"Th\u00EAm b\u00ECnh lu\u1EADn\u2026\"\n aria-label=\"N\u1ED9i dung b\u00ECnh lu\u1EADn\"\n ></textarea>\n <button\n type=\"button\"\n class=\"pp-comments__send\"\n (click)=\"submitComment()\"\n [disabled]=\"commentSaving() || !commentDraft().trim()\"\n title=\"G\u1EEDi b\u00ECnh lu\u1EADn\"\n aria-label=\"G\u1EEDi b\u00ECnh lu\u1EADn\"\n >\n @if (commentSaving()) {\n <i class=\"fa-solid fa-spinner fa-spin\" aria-hidden=\"true\"></i>\n } @else {\n <i class=\"fa-solid fa-paper-plane\" aria-hidden=\"true\"></i>\n }\n </button>\n </div>\n </section>\n </div>\n\n <footer class=\"pp-detail__foot\">\n <button\n type=\"button\"\n class=\"pp-btn pp-btn--success pp-btn--block\"\n (click)=\"openPurchaseConfirm(detail); closePendingDetail()\"\n [disabled]=\"saving()\"\n >\n <i class=\"fa-solid fa-check\" aria-hidden=\"true\"></i>\n \u0110\u00E3 mua\n </button>\n <div class=\"pp-detail__foot-row\">\n <button\n type=\"button\"\n class=\"pp-btn pp-btn--ghost\"\n (click)=\"openEdit(detail); closePendingDetail()\"\n [disabled]=\"saving()\"\n >\n <i class=\"fa-solid fa-pencil\" aria-hidden=\"true\"></i>\n S\u1EEDa\n </button>\n <button\n type=\"button\"\n class=\"pp-btn pp-btn--danger-soft\"\n (click)=\"askDeleteFromDetail(detail)\"\n [disabled]=\"saving()\"\n >\n <i class=\"fa-solid fa-trash\" aria-hidden=\"true\"></i>\n X\u00F3a\n </button>\n <button type=\"button\" class=\"pp-btn pp-btn--ghost\" (click)=\"closePendingDetail()\">\n \u0110\u00F3ng\n </button>\n </div>\n </footer>\n </div>\n </div>\n}\n\n@if (purchaseDetailProductId() && purchaseDetailItem(); as detailItem) {\n <div class=\"pp-overlay\" (click)=\"closePurchaseDetail()\" role=\"presentation\">\n <div\n class=\"pp-history-sheet\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"'L\u1ECBch s\u1EED mua ' + detailItem.itemName\"\n (click)=\"$event.stopPropagation()\"\n >\n <header class=\"pp-history-sheet__head\">\n <div>\n <h2 class=\"pp-history-sheet__title\">{{ detailItem.itemName }}</h2>\n <p class=\"pp-history-sheet__sub\">\n \u0110\u00E3 mua {{ purchaseHistoryRows().length }} l\u1EA7n\n </p>\n </div>\n <button type=\"button\" class=\"pp-icon-btn\" (click)=\"closePurchaseDetail()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\n </button>\n </header>\n\n <div class=\"pp-history-sheet__body\">\n @for (row of purchaseHistoryRows(); track itemKey(row); let idx = $index) {\n <article class=\"pp-purchase-entry\">\n <header class=\"pp-purchase-entry__head\">\n <span class=\"pp-purchase-entry__index\">L\u1EA7n {{ purchaseHistoryRows().length - idx }}</span>\n <span class=\"pp-purchase-entry__date\">\n {{ formatDate(row.purchasedDate ?? row.createdDate) }}\n </span>\n @if (row.quantity) {\n <span class=\"pp-purchase-entry__qty\">{{ row.quantity }}</span>\n }\n </header>\n\n @if (displayPurchaseNote(row) !== '\u2014') {\n <p class=\"pp-purchase-entry__note\">{{ displayPurchaseNote(row) }}</p>\n }\n\n <section class=\"pp-comments pp-comments--nested\" aria-label=\"B\u00ECnh lu\u1EADn l\u1EA7n mua\">\n <h3 class=\"pp-comments__title\">\n B\u00ECnh lu\u1EADn\n <span class=\"pp-comments__count\">{{ commentsForRequest(row.requestId).length }}</span>\n </h3>\n\n <ul class=\"pp-comments__list\">\n @for (comment of commentsForRequest(row.requestId); track comment.id) {\n <li class=\"pp-comment\">\n <div class=\"pp-comment__head\">\n <span class=\"pp-comment__author\">{{ comment.createdBy || 'guest' }}</span>\n <span class=\"pp-comment__date\">{{ formatDate(comment.createdDate) }}</span>\n </div>\n <p class=\"pp-comment__text\">{{ comment.text }}</p>\n </li>\n } @empty {\n <li class=\"pp-comments__empty\">Ch\u01B0a c\u00F3 b\u00ECnh lu\u1EADn cho l\u1EA7n mua n\u00E0y.</li>\n }\n </ul>\n\n <div class=\"pp-comments__composer\">\n <textarea\n class=\"pp-comments__input\"\n rows=\"2\"\n [value]=\"historyCommentDraft(row.requestId)\"\n (input)=\"onHistoryCommentDraft(row.requestId, $any($event.target).value)\"\n [disabled]=\"isHistoryCommentSaving(row.requestId)\"\n placeholder=\"Th\u00EAm b\u00ECnh lu\u1EADn cho l\u1EA7n mua n\u00E0y\u2026\"\n [attr.aria-label]=\"'B\u00ECnh lu\u1EADn l\u1EA7n ' + (purchaseHistoryRows().length - idx)\"\n ></textarea>\n <button\n type=\"button\"\n class=\"pp-comments__send\"\n (click)=\"submitHistoryComment(row)\"\n [disabled]=\"isHistoryCommentSaving(row.requestId) || !historyCommentDraft(row.requestId).trim()\"\n title=\"G\u1EEDi b\u00ECnh lu\u1EADn\"\n aria-label=\"G\u1EEDi b\u00ECnh lu\u1EADn\"\n >\n @if (isHistoryCommentSaving(row.requestId)) {\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i>\n } @else {\n <i class=\"pi pi-send\" aria-hidden=\"true\"></i>\n }\n </button>\n </div>\n </section>\n </article>\n }\n </div>\n\n <footer class=\"pp-history-sheet__foot\">\n <button type=\"button\" class=\"pp-btn pp-btn--ghost\" (click)=\"closePurchaseDetail()\">\u0110\u00F3ng</button>\n <button\n type=\"button\"\n class=\"pp-btn pp-btn--primary\"\n (click)=\"duplicateFromPurchased(detailItem, $event); closePurchaseDetail()\"\n >\n Mua l\u1EA1i\n </button>\n </footer>\n </div>\n </div>\n}\n\n@if (deleteConfirm()) {\n <div class=\"pp-overlay\" (click)=\"cancelDelete()\" role=\"presentation\">\n <div\n class=\"pp-confirm\"\n role=\"alertdialog\"\n aria-modal=\"true\"\n aria-label=\"Xo\u00E1 m\u00F3n\"\n (click)=\"$event.stopPropagation()\"\n >\n <h2 class=\"pp-confirm__title\">Xo\u00E1 m\u00F3n n\u00E0y?</h2>\n <p class=\"pp-confirm__body\">{{ deleteConfirm()?.itemName }}</p>\n <div class=\"pp-confirm__foot\">\n <button type=\"button\" class=\"pp-btn pp-btn--ghost\" (click)=\"cancelDelete()\" [disabled]=\"saving()\">\n Hu\u1EF7\n </button>\n <button type=\"button\" class=\"pp-btn pp-btn--danger\" (click)=\"confirmDelete()\" [disabled]=\"saving()\">\n @if (saving()) {\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i>\n \u0110ang xo\u00E1\u2026\n } @else {\n Xo\u00E1\n }\n </button>\n </div>\n </div>\n </div>\n}\n", styles: [":host{--bg: #f6f7fb;--surface: #ffffff;--border: #e6e8ef;--primary: #5b6cff;--primary-soft: #eef0ff;--text: #0f172a;--muted: #475569;--muted-2: #94a3b8;--success-soft: #ecfdf5;--success: #059669;--warning-soft: #fff7ed;--warning: #ea580c;--danger: #e11d48;--danger-soft: #fff1f2;position:fixed;inset:0;z-index:200;background:var(--bg);display:flex;flex-direction:column;color:var(--text);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;animation:pp-slide .22s cubic-bezier(.22,1,.36,1)}@keyframes pp-slide{0%{transform:translate(8px);opacity:0}to{transform:translate(0);opacity:1}}.pp-root{display:flex;flex-direction:column;flex:1;min-height:0}.pp-header{flex-shrink:0;display:flex;align-items:center;gap:6px;padding:8px 10px;padding-top:calc(8px + env(safe-area-inset-top,0px));background:var(--surface);border-bottom:1px solid var(--border)}.pp-back{width:32px;height:32px;border:none;background:transparent;border-radius:9px;color:var(--muted);font-size:15px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;-webkit-tap-highlight-color:transparent}.pp-back:hover{background:var(--primary-soft);color:var(--primary)}.pp-header__title{margin:0;font-size:16px;font-weight:700;line-height:1.15}.pp-header__spacer{flex:1}.pp-icon-btn{width:32px;height:32px;border:none;background:transparent;border-radius:9px;color:var(--muted);font-size:15px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;-webkit-tap-highlight-color:transparent}.pp-icon-btn:hover{background:var(--primary-soft);color:var(--primary)}.pp-icon-btn--danger:hover{background:var(--danger-soft);color:var(--danger)}.pp-icon-btn--success{color:var(--success)}.pp-icon-btn--success:hover{background:var(--success-soft);color:var(--success)}.pp-icon-btn__spin{animation:pp-spin .8s linear infinite}@keyframes pp-spin{to{transform:rotate(360deg)}}.pp-banner{margin:10px 12px 0;padding:10px 12px;border-radius:12px;display:flex;align-items:center;gap:8px;font-size:13px}.pp-banner--error{background:var(--danger-soft);color:var(--danger)}.pp-banner--success{background:var(--success-soft);color:var(--success)}.pp-banner__close{margin-left:auto;border:none;background:transparent;color:inherit;cursor:pointer}.pp-overview{flex-shrink:0;display:grid;grid-template-columns:repeat(4,1fr);gap:4px;padding:10px 12px 0}.pp-overview__cell{background:var(--surface);border:1px solid var(--border);border-radius:0;padding:8px 4px;text-align:center}.pp-overview__value{display:block;font-size:16px;font-weight:700;line-height:1.1}.pp-overview__value--amber{color:var(--warning)}.pp-overview__value--danger{color:var(--danger)}.pp-overview__value--success{color:var(--success)}.pp-overview__label{display:block;margin-top:2px;font-size:8px;color:var(--muted-2);line-height:1.2}.pp-priority-bar{flex-shrink:0;padding:8px 12px 0}.pp-priority-bar__track{display:flex;height:5px;overflow:hidden;background:var(--border)}.pp-priority-bar__segment{height:100%}.pp-priority-bar__segment--high{background:var(--danger)}.pp-priority-bar__segment--medium{background:#eab308}.pp-priority-bar__segment--low{background:#22c55e}.pp-priority-bar__legend{display:flex;gap:10px;margin-top:4px;font-size:9px;color:var(--muted)}.pp-tabs{flex-shrink:0;display:flex;margin:10px 12px 0;border:1px solid var(--border);background:#f7f8fc}.pp-tabs__btn{flex:1;border:none;background:transparent;padding:8px 0;font-size:11px;font-weight:700;color:var(--muted);cursor:pointer;font-family:inherit}.pp-tabs__btn:first-child{border-right:1px solid var(--border)}.pp-tabs__btn--active{background:var(--surface);color:var(--text)}.pp-tabs__count{color:var(--muted-2);font-weight:700}.pp-filters{flex-shrink:0;display:flex;gap:4px;padding:8px 12px 0;overflow-x:auto}.pp-filter-chip{flex-shrink:0;border:1px solid var(--border);border-radius:0;background:var(--surface);color:var(--muted);font-size:10px;font-weight:600;padding:4px 8px;cursor:pointer;font-family:inherit}.pp-filter-chip--active{border-color:var(--primary);background:var(--primary-soft);color:var(--primary)}.pp-group-label{margin:4px 0 0;font-size:10px;font-weight:700;letter-spacing:.03em}.pp-group-label--high{color:var(--danger)}.pp-group-label--medium{color:#d97706}.pp-group-label--low{color:var(--success)}.pp-list{flex:1;overflow:auto;padding:8px 12px 12px;display:flex;flex-direction:column;gap:5px}.pp-priority{flex-shrink:0;width:18px;padding-top:2px;display:inline-flex;align-items:flex-start;justify-content:center;font-size:12px;font-weight:700}.pp-priority--high{color:#e11d48}.pp-priority--medium{color:#eab308}.pp-priority--low{color:#22c55e}.pp-priority--muted{opacity:.55}.pp-row{position:relative;display:flex;gap:6px;align-items:flex-start;padding:9px 10px;background:var(--surface);border:1px solid var(--border);border-radius:0}.pp-row--pending{border-left-width:3px}.pp-row--high{border-left-color:var(--danger)}.pp-row--medium{border-left-color:#eab308}.pp-row--low{border-left-color:#22c55e}.pp-row--purchased{opacity:.95;background:#fafbfc}.pp-row--clickable{cursor:pointer;transition:border-color .15s ease,background .15s ease}.pp-row--clickable:hover{border-color:color-mix(in srgb,var(--primary) 35%,var(--border));background:#f8fafc}.pp-row--clickable:focus-visible{outline:2px solid var(--primary);outline-offset:2px}.pp-row--menu-open{z-index:10;overflow:visible}.pp-row--clickable{cursor:pointer;transition:background .15s ease}.pp-row--clickable:hover{background:#f8fafc}.pp-row--clickable:focus-visible{outline:2px solid var(--primary);outline-offset:-2px}.pp-row__body{flex:1;min-width:0}.pp-row__top{display:flex;align-items:baseline;justify-content:space-between;gap:8px;margin-bottom:2px}.pp-row__main{flex:1;min-width:0}.pp-row__title{margin:0;font-size:14px;font-weight:700;line-height:1.25;min-width:0}.pp-row--compact .pp-row__title{font-size:14px}.pp-row__desc{margin:0 0 4px;font-size:12px;color:var(--muted);line-height:1.35;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.pp-row__qty{flex-shrink:0;font-size:12px;color:var(--primary);font-weight:700;white-space:nowrap}.pp-row__note{margin:0 0 4px;font-size:12px;color:var(--muted);line-height:1.35;display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden}.pp-row__meta{margin:4px 0 0;font-size:9px;color:var(--muted-2);min-width:0}.pp-row__comments{display:inline-flex;align-items:center;gap:3px;white-space:nowrap}.pp-row__comments i{font-size:9px}.pp-row__actions-col{display:flex;flex-direction:column;align-items:center;gap:6px;flex-shrink:0}.pp-row__purchase-note{margin:0 0 6px;font-size:13px;color:var(--success);line-height:1.4}.pp-row__purchase-note i{margin-right:4px;font-size:12px}.pp-row__confirm{flex-shrink:0;width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--success);border-radius:0;background:var(--success-soft);color:var(--success);font-size:12px;cursor:pointer;font-family:inherit;-webkit-tap-highlight-color:transparent}.pp-row__confirm:hover{background:var(--success);color:#fff}.pp-row__confirm:disabled{opacity:.6;cursor:not-allowed}.pp-row__menu-wrap{position:relative;flex-shrink:0;align-self:center}.pp-icon-btn--menu{color:var(--muted-2)}.pp-row-menu{position:absolute;top:calc(100% + 4px);right:0;min-width:148px;background:var(--surface);border:1px solid var(--border);border-radius:0;box-shadow:0 8px 24px #0f172a1f;padding:4px;z-index:20}.pp-row-menu__item{width:100%;border:none;background:transparent;border-radius:0;padding:8px 10px;display:inline-flex;align-items:center;gap:8px;font-size:12px;font-weight:600;color:var(--text);cursor:pointer;text-align:left;font-family:inherit}.pp-row-menu__item:not(:last-child){border-bottom:1px solid var(--border)}.pp-row-menu__item:hover{background:var(--primary-soft);color:var(--primary)}.pp-row-menu__item--danger{color:var(--danger)}.pp-row-menu__item--danger:hover{background:var(--danger-soft);color:var(--danger)}.pp-row-menu__badge{margin-left:auto;font-size:11px;font-weight:700;padding:2px 7px;border-radius:999px;background:var(--primary-soft);color:var(--primary)}.pp-empty{margin:12px 0;text-align:center;color:var(--muted-2);font-size:14px}.pp-empty--group{margin:8px 0 0;font-size:13px}.pp-add-wrap{flex-shrink:0;padding:12px;padding-bottom:calc(12px + env(safe-area-inset-bottom,0px));background:var(--surface);border-top:1px solid var(--border)}.pp-add{width:100%;border:none;border-radius:0;padding:11px 16px;background:var(--primary);color:#fff;font-size:15px;font-weight:700;display:inline-flex;align-items:center;justify-content:center;gap:8px;cursor:pointer}.pp-add:disabled{opacity:.6;cursor:not-allowed}.pp-overlay{position:fixed;inset:0;z-index:420;background:#0f172a80;display:flex;align-items:center;justify-content:center;padding:16px;padding-top:calc(16px + env(safe-area-inset-top,0px));padding-bottom:calc(16px + env(safe-area-inset-bottom,0px))}.pp-overlay--detail{background:#0f172a61;padding:12px}.pp-confirm{width:100%;max-width:360px;background:var(--surface);border-radius:0;padding:18px 16px}.pp-confirm__title{margin:0 0 8px;font-size:16px}.pp-confirm__body{margin:0 0 16px;color:var(--muted);font-size:14px}.pp-confirm__foot{display:flex;justify-content:flex-end;gap:8px}.pp-btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;border:none;border-radius:10px;padding:9px 16px;font-size:14px;font-weight:600;cursor:pointer}.pp-btn--ghost{background:transparent;color:var(--muted)}.pp-btn--danger{background:var(--danger);color:#fff}.pp-btn--primary{background:var(--primary);color:#fff}.pp-btn--success{background:var(--success-soft);border:1px solid var(--success);color:var(--success)}.pp-btn--block{width:100%}.pp-btn--danger-soft{background:color-mix(in srgb,var(--danger) 10%,var(--surface));border:1px solid color-mix(in srgb,var(--danger) 35%,var(--border));color:var(--danger)}.pp-icon-btn--soft{width:34px;height:34px;border-radius:10px;background:var(--surface-2, #f1f5f9);color:var(--muted)}.pp-detail{width:100%;max-width:420px;max-height:min(90vh,720px);background:var(--surface);border:1px solid var(--border);border-radius:16px;display:flex;flex-direction:column;overflow:hidden}.pp-detail__hero{padding:16px 16px 12px;border-bottom:1px solid color-mix(in srgb,var(--border) 70%,transparent)}.pp-detail__hero-top{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:10px}.pp-detail__priority{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:999px;font-size:11px;font-weight:700;background:var(--surface-2, #f1f5f9);color:var(--muted)}.pp-detail__priority--high{background:color-mix(in srgb,var(--danger) 12%,white);color:var(--danger)}.pp-detail__priority--medium{background:color-mix(in srgb,#d97706 12%,white);color:#d97706}.pp-detail__priority--low{background:color-mix(in srgb,var(--success) 12%,white);color:var(--success)}.pp-detail__priority .fa-solid{font-size:10px}.pp-detail__name{margin:0 0 8px;font-size:20px;font-weight:800;line-height:1.25;letter-spacing:-.02em;color:var(--text);overflow-wrap:anywhere}.pp-detail__meta{margin:0;display:flex;flex-wrap:wrap;gap:12px;font-size:12px;font-weight:600;color:var(--muted)}.pp-detail__meta span{display:inline-flex;align-items:center;gap:6px}.pp-detail__meta .fa-solid{font-size:11px;opacity:.75}.pp-detail__body{flex:1;overflow:auto;padding:14px 16px 16px;display:flex;flex-direction:column;gap:10px}.pp-detail__foot{display:flex;flex-direction:column;gap:8px;padding:12px 16px 16px;border-top:1px solid color-mix(in srgb,var(--border) 70%,transparent);background:color-mix(in srgb,var(--surface-2, #f8fafc) 50%,var(--surface))}.pp-detail__foot-row{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}.pp-detail__foot-row .pp-btn{width:100%;padding:9px 8px;font-size:13px}.pp-detail-card{padding:12px 14px;border-radius:14px;background:var(--surface-2, #f8fafc);border:1px solid color-mix(in srgb,var(--border) 80%,transparent)}.pp-detail-card__title{margin:0 0 8px;font-size:12px;font-weight:700;color:var(--muted)}.pp-detail-card__text{margin:0;font-size:13px;line-height:1.5;color:var(--text);overflow-wrap:anywhere}.pp-detail-kv{margin:0}.pp-detail-kv__row{display:flex;align-items:baseline;justify-content:space-between;gap:12px;padding:5px 0}.pp-detail-kv__row:not(:last-child){border-bottom:1px solid color-mix(in srgb,var(--border) 55%,transparent)}.pp-detail-kv__row dt{margin:0;font-size:12px;font-weight:500;color:var(--muted)}.pp-detail-kv__row dd{margin:0;font-size:13px;font-weight:700;color:var(--text);text-align:right;overflow-wrap:anywhere}.pp-detail-kv__value--overdue{color:var(--danger)}.pp-comments{margin-top:4px}.pp-comments__head{display:flex;align-items:center;gap:8px;margin-bottom:10px}.pp-comments__title{margin:0;font-size:13px;font-weight:700;color:var(--text)}.pp-comments__count{font-size:11px;font-weight:700;min-width:22px;height:22px;padding:0 7px;display:inline-flex;align-items:center;justify-content:center;border-radius:999px;background:var(--primary-soft);color:var(--primary)}.pp-comments__list{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:8px}.pp-comments__empty{font-size:12px;line-height:1.45;color:var(--muted-2);padding:10px 12px;border-radius:12px;background:var(--surface-2, #f8fafc);border:1px dashed color-mix(in srgb,var(--border) 90%,transparent)}.pp-comments__composer{display:flex;align-items:flex-end;gap:8px;margin-top:12px}.pp-comments__input{flex:1;min-width:0;resize:vertical;min-height:44px;padding:10px 12px;border:1px solid color-mix(in srgb,var(--border) 90%,transparent);border-radius:12px;background:var(--surface);color:var(--text);font-size:13px;font-family:inherit;outline:none;line-height:1.4}.pp-comments__input:focus{border-color:var(--primary)}.pp-comments__send{flex-shrink:0;width:40px;height:40px;display:inline-flex;align-items:center;justify-content:center;border:none;border-radius:12px;background:var(--primary);color:#fff;font-size:14px;cursor:pointer;font-family:inherit;-webkit-tap-highlight-color:transparent}.pp-comments__send:hover:not(:disabled){filter:brightness(1.05)}.pp-comments__send:disabled{opacity:.5;cursor:not-allowed}.pp-comment{border-radius:12px;background:var(--surface-2, #f8fafc);border:1px solid color-mix(in srgb,var(--border) 80%,transparent);padding:10px 12px}.pp-comment__head{display:flex;align-items:baseline;justify-content:space-between;gap:8px;margin-bottom:6px}.pp-comment__author{font-size:12px;font-weight:700;color:var(--text)}.pp-comment__date{font-size:10px;font-weight:600;color:var(--muted-2);font-variant-numeric:tabular-nums}.pp-comment__text{margin:0;font-size:13px;color:var(--muted);line-height:1.45;overflow-wrap:anywhere;white-space:pre-wrap}.pp-comment__link{color:var(--primary);text-decoration:underline;text-underline-offset:2px;word-break:break-all}.pp-comment__link:hover{filter:brightness(.92)}.pp-purchase-entry{border:1px solid var(--border);background:#fafbfc;padding:12px;margin-bottom:10px}.pp-purchase-entry:last-child{margin-bottom:0}.pp-purchase-entry__head{display:flex;flex-wrap:wrap;align-items:baseline;gap:6px 10px;margin-bottom:8px}.pp-purchase-entry__index{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.03em;color:var(--primary)}.pp-purchase-entry__date{font-size:13px;font-weight:700;color:var(--text)}.pp-purchase-entry__qty{margin-left:auto;font-size:12px;font-weight:700;color:var(--muted)}.pp-purchase-entry__note{margin:0 0 10px;font-size:12px;line-height:1.4;color:var(--muted);overflow-wrap:anywhere}.pp-comments--nested{margin-top:0;padding-top:0;border-top:none}.pp-history-sheet{width:min(100%,520px);max-height:min(92vh,720px);margin:0 auto;background:var(--surface);border-radius:0;display:flex;flex-direction:column;overflow:hidden}.pp-history-sheet__head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding:16px;border-bottom:1px solid var(--border)}.pp-history-sheet__title{margin:0;font-size:17px;font-weight:700}.pp-history-sheet__sub{margin:4px 0 0;font-size:13px;color:var(--muted)}.pp-history-sheet__body{flex:1;overflow:auto;padding:12px 16px}.pp-history-sheet__foot{display:flex;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid var(--border)}.pp-history-table{width:100%;border-collapse:collapse;font-size:13px}.pp-history-table th,.pp-history-table td{padding:10px 8px;text-align:left;border-bottom:1px solid var(--border);vertical-align:top}.pp-history-table th{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.03em;color:var(--muted-2);background:#f8fafc;position:sticky;top:0}.pp-history-table tbody tr:last-child td{border-bottom:none}.pp-history-table td:first-child{width:36px;color:var(--muted-2);font-weight:700}.pp-history-table td:nth-child(3){white-space:nowrap}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: PreparingPurchaseDialogComponent, selector: "app-preparing-purchase-dialog", inputs: ["mode", "heading", "products", "initialProductId", "initialProductName", "initialProductDescription", "initialAmount", "initialNote", "initialAssignee", "initialDueDate", "initialPriority", "saving"], outputs: ["cancel", "save"] }, { kind: "component", type: PreparingPurchaseConfirmDialogComponent, selector: "app-preparing-purchase-confirm-dialog", inputs: ["itemName", "quantity", "saving"], outputs: ["cancel", "confirm"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
23742
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: PreparingPurchaseComponent, isStandalone: true, selector: "app-preparing-purchase", inputs: { user: { classPropertyName: "user", publicName: "user", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { back: "back", pendingCountChange: "pendingCountChange" }, host: { listeners: { "document:click": "onDocumentClick()" } }, ngImport: i0, template: "<section class=\"pp-root\" role=\"dialog\" aria-modal=\"true\" aria-label=\"C\u1EA7n mua s\u1EAFm\">\n <header class=\"pp-header\">\n <button type=\"button\" class=\"pp-back\" (click)=\"back.emit()\" aria-label=\"Quay l\u1EA1i\">\n <i class=\"pi pi-chevron-left\" aria-hidden=\"true\"></i>\n </button>\n <h1 class=\"pp-header__title\">C\u1EA7n mua s\u1EAFm</h1>\n <span class=\"pp-header__spacer\"></span>\n <button\n type=\"button\"\n class=\"pp-icon-btn\"\n (click)=\"load(true)\"\n [disabled]=\"loading()\"\n title=\"L\u00E0m m\u1EDBi danh s\u00E1ch\"\n aria-label=\"L\u00E0m m\u1EDBi danh s\u00E1ch\"\n >\n <i class=\"pi pi-refresh\" [class.pp-icon-btn__spin]=\"loading()\" aria-hidden=\"true\"></i>\n </button>\n </header>\n\n @if (errorMsg()) {\n <div class=\"pp-banner pp-banner--error\" role=\"alert\">\n <span>{{ errorMsg() }}</span>\n <button type=\"button\" class=\"pp-banner__close\" (click)=\"clearMessages()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\n </button>\n </div>\n }\n\n @if (successMsg()) {\n <div class=\"pp-banner pp-banner--success\" role=\"status\">\n <span>{{ successMsg() }}</span>\n <button type=\"button\" class=\"pp-banner__close\" (click)=\"clearMessages()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\n </button>\n </div>\n }\n\n <div class=\"pp-overview\" aria-label=\"T\u1ED5ng quan\">\n <div class=\"pp-overview__cell\">\n <span class=\"pp-overview__value pp-overview__value--amber\">{{ pendingItems().length }}</span>\n <span class=\"pp-overview__label\">Ch\u01B0a mua</span>\n </div>\n <div class=\"pp-overview__cell\">\n <span class=\"pp-overview__value pp-overview__value--danger\">{{ highPriorityCount() }}</span>\n <span class=\"pp-overview__label\">\u01AFu ti\u00EAn cao</span>\n </div>\n <div class=\"pp-overview__cell\">\n <span class=\"pp-overview__value pp-overview__value--danger\">{{ overdueCount() }}</span>\n <span class=\"pp-overview__label\">Qu\u00E1 h\u1EA1n</span>\n </div>\n <div class=\"pp-overview__cell\">\n <span class=\"pp-overview__value pp-overview__value--success\">{{ purchasedThisMonthCount() }}</span>\n <span class=\"pp-overview__label\">\u0110\u00E3 mua th\u00E1ng</span>\n </div>\n </div>\n\n @if (pendingItems().length > 0) {\n <div class=\"pp-priority-bar\" aria-hidden=\"true\">\n <div class=\"pp-priority-bar__track\">\n @for (segment of prioritySegments(); track segment.priority) {\n <span\n class=\"pp-priority-bar__segment\"\n [class.pp-priority-bar__segment--high]=\"segment.priority === 'high'\"\n [class.pp-priority-bar__segment--medium]=\"segment.priority === 'medium'\"\n [class.pp-priority-bar__segment--low]=\"segment.priority === 'low'\"\n [style.width.%]=\"segment.percent\"\n ></span>\n }\n </div>\n <div class=\"pp-priority-bar__legend\">\n <span>\u2191 Cao {{ priorityCounts().high }}</span>\n <span>\u2014 TB {{ priorityCounts().medium }}</span>\n <span>\u2193 Th\u1EA5p {{ priorityCounts().low }}</span>\n </div>\n </div>\n }\n\n <div class=\"pp-tabs\" role=\"tablist\" aria-label=\"Tr\u1EA1ng th\u00E1i mua\">\n <button\n type=\"button\"\n class=\"pp-tabs__btn\"\n role=\"tab\"\n [class.pp-tabs__btn--active]=\"activeTab() === 'pending'\"\n [attr.aria-selected]=\"activeTab() === 'pending'\"\n (click)=\"setTab('pending')\"\n >\n C\u1EA7n mua <span class=\"pp-tabs__count\">{{ pendingItems().length }}</span>\n </button>\n <button\n type=\"button\"\n class=\"pp-tabs__btn\"\n role=\"tab\"\n [class.pp-tabs__btn--active]=\"activeTab() === 'purchased'\"\n [attr.aria-selected]=\"activeTab() === 'purchased'\"\n (click)=\"setTab('purchased')\"\n >\n \u0110\u00E3 mua <span class=\"pp-tabs__count\">{{ purchasedItems().length }}</span>\n </button>\n </div>\n\n @if (activeTab() === 'pending') {\n <div class=\"pp-filters\" role=\"group\" aria-label=\"L\u1ECDc theo \u01B0u ti\u00EAn\">\n @for (chip of priorityFilterOptions; track chip.id) {\n <button\n type=\"button\"\n class=\"pp-filter-chip\"\n [class.pp-filter-chip--active]=\"priorityFilter() === chip.id\"\n (click)=\"setPriorityFilter(chip.id)\"\n >\n {{ chip.label }}\n </button>\n }\n </div>\n }\n\n <div class=\"pp-list\">\n @if (activeTab() === 'pending') {\n @if (priorityFilter() === 'all') {\n @for (group of pendingPriorityGroups(); track group.priority) {\n @if (group.items.length > 0) {\n <p\n class=\"pp-group-label\"\n [class.pp-group-label--high]=\"group.priority === 'high'\"\n [class.pp-group-label--medium]=\"group.priority === 'medium'\"\n [class.pp-group-label--low]=\"group.priority === 'low'\"\n >\n {{ priorityGroupHeading(group.priority) }}\n </p>\n @for (item of group.items; track itemKey(item)) {\n <ng-container *ngTemplateOutlet=\"pendingRow; context: { $implicit: item }\" />\n }\n }\n }\n } @else {\n <p\n class=\"pp-group-label\"\n [class.pp-group-label--high]=\"priorityFilter() === 'high'\"\n [class.pp-group-label--medium]=\"priorityFilter() === 'medium'\"\n [class.pp-group-label--low]=\"priorityFilter() === 'low'\"\n >\n {{ filteredPriorityHeading() }}\n </p>\n @for (item of filteredPendingItems(); track itemKey(item)) {\n <ng-container *ngTemplateOutlet=\"pendingRow; context: { $implicit: item }\" />\n }\n }\n\n @if (filteredPendingItems().length === 0 && !loading()) {\n <p class=\"pp-empty\">Kh\u00F4ng c\u00F2n m\u00F3n n\u00E0o c\u1EA7n mua.</p>\n }\n } @else {\n @for (item of purchasedItems(); track itemKey(item)) {\n <ng-container *ngTemplateOutlet=\"purchasedRow; context: { $implicit: item }\" />\n } @empty {\n @if (!loading()) {\n <p class=\"pp-empty\">Ch\u01B0a c\u00F3 m\u00F3n n\u00E0o \u0111\u00E3 mua.</p>\n }\n }\n }\n\n @if (loading() && pendingItems().length === 0 && purchasedItems().length === 0) {\n <p class=\"pp-empty\">\u0110ang t\u1EA3i\u2026</p>\n }\n </div>\n\n <div class=\"pp-add-wrap\">\n <button type=\"button\" class=\"pp-add\" (click)=\"openAdd()\" [disabled]=\"saving()\">\n <i class=\"pi pi-plus\" aria-hidden=\"true\"></i>\n <span>Th\u00EAm m\u00F3n</span>\n </button>\n </div>\n</section>\n\n<ng-template #pendingRow let-item>\n <article\n class=\"pp-row pp-row--pending pp-row--clickable\"\n [class.pp-row--menu-open]=\"rowMenuKey() === itemKey(item)\"\n [class.pp-row--high]=\"item.priority === 'high'\"\n [class.pp-row--medium]=\"item.priority === 'medium'\"\n [class.pp-row--low]=\"item.priority === 'low'\"\n (click)=\"openPendingDetailItem(item)\"\n role=\"button\"\n tabindex=\"0\"\n (keydown.enter)=\"openPendingDetailItem(item)\"\n (keydown.space)=\"$event.preventDefault(); openPendingDetailItem(item)\"\n [attr.aria-label]=\"'Xem chi ti\u1EBFt ' + item.itemName\"\n >\n <span\n class=\"pp-priority\"\n [class.pp-priority--high]=\"item.priority === 'high'\"\n [class.pp-priority--medium]=\"item.priority === 'medium'\"\n [class.pp-priority--low]=\"item.priority === 'low'\"\n [attr.title]=\"'\u01AFu ti\u00EAn ' + priorityLabel(item.priority)\"\n >\n <i class=\"fa-solid\" [ngClass]=\"priorityIcon(item.priority)\" aria-hidden=\"true\"></i>\n </span>\n\n <div class=\"pp-row__body\">\n <div class=\"pp-row__top\">\n <h2 class=\"pp-row__title\">{{ item.itemName }}</h2>\n @if (item.quantity) {\n <span class=\"pp-row__qty\">{{ item.quantity }}</span>\n }\n </div>\n @if (rowSubtitle(item); as subtitle) {\n <p class=\"pp-row__desc\">{{ subtitle }}</p>\n }\n <p class=\"pp-row__meta\">\n Th\u00EAm {{ formatDate(item.createdDate) }}\n @if (item.createdBy) {\n \u00B7 {{ item.createdBy }}\n }\n <span class=\"pp-row__comments\" [attr.title]=\"commentCountForRequest(item.requestId) + ' b\u00ECnh lu\u1EADn'\">\n \u00B7 <i class=\"pi pi-comment\" aria-hidden=\"true\"></i>\n {{ commentCountForRequest(item.requestId) }}\n </span>\n </p>\n </div>\n\n <div class=\"pp-row__actions-col\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"pp-row__confirm\"\n (click)=\"openPurchaseConfirm(item)\"\n [disabled]=\"saving()\"\n title=\"\u0110\u00E1nh d\u1EA5u \u0111\u00E3 mua\"\n aria-label=\"\u0110\u00E1nh d\u1EA5u \u0111\u00E3 mua\"\n >\n <i class=\"pi pi-check\" aria-hidden=\"true\"></i>\n </button>\n <div class=\"pp-row__menu-wrap\">\n <button\n type=\"button\"\n class=\"pp-icon-btn pp-icon-btn--menu\"\n (click)=\"toggleRowMenu(item, $event)\"\n title=\"Tu\u1EF3 ch\u1ECDn\"\n aria-label=\"Tu\u1EF3 ch\u1ECDn\"\n aria-haspopup=\"menu\"\n [attr.aria-expanded]=\"rowMenuKey() === itemKey(item)\"\n >\n <i class=\"pi pi-ellipsis-v\" aria-hidden=\"true\"></i>\n </button>\n @if (rowMenuKey() === itemKey(item)) {\n <div class=\"pp-row-menu\" role=\"menu\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"pp-row-menu__item\"\n role=\"menuitem\"\n (click)=\"openPendingDetail(item, $event)\"\n >\n Xem chi ti\u1EBFt\n </button>\n <button\n type=\"button\"\n class=\"pp-row-menu__item\"\n role=\"menuitem\"\n (click)=\"openEdit(item); rowMenuKey.set(null)\"\n >\n S\u1EEDa\n </button>\n <button\n type=\"button\"\n class=\"pp-row-menu__item pp-row-menu__item--danger\"\n role=\"menuitem\"\n (click)=\"askDelete(item, $event)\"\n >\n Xo\u00E1\n </button>\n </div>\n }\n </div>\n </div>\n </article>\n</ng-template>\n\n<ng-template #purchasedRow let-item>\n <article\n class=\"pp-row pp-row--purchased pp-row--clickable\"\n [class.pp-row--menu-open]=\"rowMenuKey() === itemKey(item)\"\n (click)=\"openPurchaseDetail(item, $event)\"\n role=\"button\"\n tabindex=\"0\"\n (keydown.enter)=\"openPurchaseDetail(item, $event)\"\n (keydown.space)=\"$event.preventDefault(); openPurchaseDetailItem(item)\"\n [attr.aria-label]=\"'Xem l\u1ECBch s\u1EED mua ' + item.itemName\"\n >\n <span\n class=\"pp-priority pp-priority--muted\"\n [class.pp-priority--high]=\"item.priority === 'high'\"\n [class.pp-priority--medium]=\"item.priority === 'medium'\"\n [class.pp-priority--low]=\"item.priority === 'low'\"\n [attr.title]=\"'\u01AFu ti\u00EAn ' + priorityLabel(item.priority)\"\n >\n <i class=\"fa-solid\" [ngClass]=\"priorityIcon(item.priority)\" aria-hidden=\"true\"></i>\n </span>\n\n <div class=\"pp-row__body\">\n <div class=\"pp-row__top\">\n <h2 class=\"pp-row__title\">{{ item.itemName }}</h2>\n @if (item.quantity) {\n <span class=\"pp-row__qty\">{{ item.quantity }}</span>\n }\n </div>\n <p class=\"pp-row__desc\">\n @if (item.description) {\n {{ item.description }}\n } @else if (item.purchasedDate) {\n Mua {{ formatDate(item.purchasedDate) }}\n }\n </p>\n <p class=\"pp-row__meta\">\n @if (item.purchasedDate) {\n \u0110\u00E3 mua {{ formatDate(item.purchasedDate) }}\n }\n \u00B7 {{ purchaseCountForProduct(item.productId) }} l\u1EA7n\n <span class=\"pp-row__comments\" [attr.title]=\"commentCountForRequest(item.requestId) + ' b\u00ECnh lu\u1EADn'\">\n \u00B7 <i class=\"pi pi-comment\" aria-hidden=\"true\"></i>\n {{ commentCountForRequest(item.requestId) }}\n </span>\n </p>\n </div>\n\n <div class=\"pp-row__menu-wrap\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"pp-icon-btn pp-icon-btn--menu\"\n (click)=\"toggleRowMenu(item, $event)\"\n title=\"Tu\u1EF3 ch\u1ECDn\"\n aria-label=\"Tu\u1EF3 ch\u1ECDn\"\n aria-haspopup=\"menu\"\n [attr.aria-expanded]=\"rowMenuKey() === itemKey(item)\"\n >\n <i class=\"pi pi-ellipsis-v\" aria-hidden=\"true\"></i>\n </button>\n @if (rowMenuKey() === itemKey(item)) {\n <div class=\"pp-row-menu\" role=\"menu\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"pp-row-menu__item\"\n role=\"menuitem\"\n (click)=\"openPurchaseDetail(item, $event)\"\n >\n Xem chi ti\u1EBFt\n <span class=\"pp-row-menu__badge\">{{ purchaseCountForProduct(item.productId) }}</span>\n </button>\n <button\n type=\"button\"\n class=\"pp-row-menu__item\"\n role=\"menuitem\"\n (click)=\"duplicateFromPurchased(item, $event)\"\n >\n Mua l\u1EA1i\n </button>\n </div>\n }\n </div>\n </article>\n</ng-template>\n\n@if (isDialogOpen()) {\n <app-preparing-purchase-dialog\n [heading]=\"dialogHeading()\"\n [mode]=\"dialogMode()\"\n [products]=\"products()\"\n [initialProductId]=\"dialogProductId()\"\n [initialProductName]=\"dialogProductName()\"\n [initialProductDescription]=\"dialogProductDescription()\"\n [initialAmount]=\"dialogAmount()\"\n [initialNote]=\"dialogNote()\"\n [initialAssignee]=\"dialogAssignee()\"\n [initialDueDate]=\"dialogDueDate()\"\n [initialPriority]=\"dialogPriority()\"\n [saving]=\"saving()\"\n (cancel)=\"closeDialog()\"\n (save)=\"onSave($event)\"\n />\n}\n\n@if (purchaseConfirm()) {\n <app-preparing-purchase-confirm-dialog\n [itemName]=\"purchaseConfirm()?.itemName ?? ''\"\n [quantity]=\"purchaseConfirm()?.quantity ?? ''\"\n [saving]=\"saving()\"\n (cancel)=\"cancelPurchaseConfirm()\"\n (confirm)=\"onConfirmPurchase($event)\"\n />\n}\n\n@if (pendingDetailItem(); as detail) {\n <div class=\"pp-overlay pp-overlay--detail\" (click)=\"closePendingDetail()\" role=\"presentation\">\n <div\n class=\"pp-detail\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"detail.itemName\"\n (click)=\"$event.stopPropagation()\"\n >\n <header class=\"pp-detail__hero\">\n <div class=\"pp-detail__hero-top\">\n <span\n class=\"pp-detail__priority\"\n [class.pp-detail__priority--high]=\"detail.priority === 'high'\"\n [class.pp-detail__priority--medium]=\"detail.priority === 'medium'\"\n [class.pp-detail__priority--low]=\"detail.priority === 'low'\"\n >\n <i class=\"fa-solid\" [ngClass]=\"priorityIcon(detail.priority)\" aria-hidden=\"true\"></i>\n {{ priorityLabel(detail.priority) }}\n </span>\n <button type=\"button\" class=\"pp-icon-btn pp-icon-btn--soft\" (click)=\"closePendingDetail()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"fa-solid fa-xmark\" aria-hidden=\"true\"></i>\n </button>\n </div>\n <h2 class=\"pp-detail__name\">{{ detail.itemName }}</h2>\n <p class=\"pp-detail__meta\">\n <span>\n <i class=\"fa-solid fa-user\" aria-hidden=\"true\"></i>\n {{ detail.createdBy || 'guest' }}\n </span>\n <span>\n <i class=\"fa-solid fa-clock\" aria-hidden=\"true\"></i>\n {{ formatDateTime(detail.createdDate) }}\n </span>\n </p>\n </header>\n\n <div class=\"pp-detail__body\">\n <section class=\"pp-detail-card\" aria-label=\"Y\u00EAu c\u1EA7u mua\">\n <h3 class=\"pp-detail-card__title\">Y\u00EAu c\u1EA7u mua</h3>\n <dl class=\"pp-detail-kv\">\n <div class=\"pp-detail-kv__row\">\n <dt>S\u1ED1 l\u01B0\u1EE3ng</dt>\n <dd>{{ detail.quantity || '\u2014' }}</dd>\n </div>\n <div class=\"pp-detail-kv__row\">\n <dt>Ng\u01B0\u1EDDi ph\u1EE5 tr\u00E1ch</dt>\n <dd>{{ itemAssignee(detail) || '\u2014' }}</dd>\n </div>\n <div class=\"pp-detail-kv__row\">\n <dt>Th\u1EDDi h\u1EA1n mua</dt>\n <dd [class.pp-detail-kv__value--overdue]=\"isItemOverdue(detail)\">\n {{ itemDueDate(detail) || '\u2014' }}\n </dd>\n </div>\n </dl>\n </section>\n\n @if (detail.description?.trim()) {\n <section class=\"pp-detail-card\" aria-label=\"M\u00F4 t\u1EA3 s\u1EA3n ph\u1EA9m\">\n <h3 class=\"pp-detail-card__title\">M\u00F4 t\u1EA3</h3>\n <p class=\"pp-detail-card__text\">{{ detail.description }}</p>\n </section>\n }\n\n @if (itemNoteText(detail); as noteText) {\n <section class=\"pp-detail-card\" aria-label=\"Ghi ch\u00FA\">\n <h3 class=\"pp-detail-card__title\">Ghi ch\u00FA</h3>\n <p class=\"pp-detail-card__text\">{{ noteText }}</p>\n </section>\n }\n\n <section class=\"pp-comments\" aria-label=\"B\u00ECnh lu\u1EADn\">\n <div class=\"pp-comments__head\">\n <h3 class=\"pp-comments__title\">B\u00ECnh lu\u1EADn</h3>\n <span class=\"pp-comments__count\">{{ detailComments().length }}</span>\n </div>\n\n <ul class=\"pp-comments__list\">\n @for (comment of detailComments(); track comment.id) {\n <li class=\"pp-comment\">\n <div class=\"pp-comment__head\">\n <span class=\"pp-comment__author\">{{ comment.createdBy || 'guest' }}</span>\n <span class=\"pp-comment__date\">{{ formatDateTime(comment.createdDate) }}</span>\n </div>\n <p class=\"pp-comment__text\">\n @for (part of commentSegments(comment.text); track $index) {\n @if (part.type === 'link') {\n <a\n class=\"pp-comment__link\"\n [href]=\"part.href\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n (click)=\"$event.stopPropagation()\"\n >{{ part.value }}</a>\n } @else {\n {{ part.value }}\n }\n }\n </p>\n </li>\n } @empty {\n <li class=\"pp-comments__empty\">Ch\u01B0a c\u00F3 b\u00ECnh lu\u1EADn \u2014 th\u00EAm ghi ch\u00FA cho m\u1ECDi ng\u01B0\u1EDDi c\u00F9ng xem.</li>\n }\n </ul>\n\n <div class=\"pp-comments__composer\">\n <textarea\n class=\"pp-comments__input\"\n rows=\"2\"\n [value]=\"commentDraft()\"\n (input)=\"onCommentDraft($any($event.target).value)\"\n [disabled]=\"commentSaving()\"\n placeholder=\"Th\u00EAm b\u00ECnh lu\u1EADn\u2026\"\n aria-label=\"N\u1ED9i dung b\u00ECnh lu\u1EADn\"\n ></textarea>\n <button\n type=\"button\"\n class=\"pp-comments__send\"\n (click)=\"submitComment()\"\n [disabled]=\"commentSaving() || !commentDraft().trim()\"\n title=\"G\u1EEDi b\u00ECnh lu\u1EADn\"\n aria-label=\"G\u1EEDi b\u00ECnh lu\u1EADn\"\n >\n @if (commentSaving()) {\n <i class=\"fa-solid fa-spinner fa-spin\" aria-hidden=\"true\"></i>\n } @else {\n <i class=\"fa-solid fa-paper-plane\" aria-hidden=\"true\"></i>\n }\n </button>\n </div>\n </section>\n </div>\n\n <footer class=\"pp-detail__foot\">\n <button\n type=\"button\"\n class=\"pp-btn pp-btn--success pp-btn--block\"\n (click)=\"openPurchaseConfirm(detail); closePendingDetail()\"\n [disabled]=\"saving()\"\n >\n <i class=\"fa-solid fa-check\" aria-hidden=\"true\"></i>\n \u0110\u00E3 mua\n </button>\n <div class=\"pp-detail__foot-row\">\n <button\n type=\"button\"\n class=\"pp-btn pp-btn--ghost\"\n (click)=\"openEdit(detail); closePendingDetail()\"\n [disabled]=\"saving()\"\n >\n <i class=\"fa-solid fa-pencil\" aria-hidden=\"true\"></i>\n S\u1EEDa\n </button>\n <button\n type=\"button\"\n class=\"pp-btn pp-btn--danger-soft\"\n (click)=\"askDeleteFromDetail(detail)\"\n [disabled]=\"saving()\"\n >\n <i class=\"fa-solid fa-trash\" aria-hidden=\"true\"></i>\n X\u00F3a\n </button>\n <button type=\"button\" class=\"pp-btn pp-btn--ghost\" (click)=\"closePendingDetail()\">\n \u0110\u00F3ng\n </button>\n </div>\n </footer>\n </div>\n </div>\n}\n\n@if (purchaseDetailProductId() && purchaseDetailItem(); as detailItem) {\n <div class=\"pp-overlay\" (click)=\"closePurchaseDetail()\" role=\"presentation\">\n <div\n class=\"pp-history-sheet\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"'L\u1ECBch s\u1EED mua ' + detailItem.itemName\"\n (click)=\"$event.stopPropagation()\"\n >\n <header class=\"pp-history-sheet__head\">\n <div>\n <h2 class=\"pp-history-sheet__title\">{{ detailItem.itemName }}</h2>\n <p class=\"pp-history-sheet__sub\">\n \u0110\u00E3 mua {{ purchaseHistoryRows().length }} l\u1EA7n\n </p>\n </div>\n <button type=\"button\" class=\"pp-icon-btn\" (click)=\"closePurchaseDetail()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\n </button>\n </header>\n\n <div class=\"pp-history-sheet__body\">\n @for (row of purchaseHistoryRows(); track itemKey(row); let idx = $index) {\n <article class=\"pp-purchase-entry\">\n <header class=\"pp-purchase-entry__head\">\n <span class=\"pp-purchase-entry__index\">L\u1EA7n {{ purchaseHistoryRows().length - idx }}</span>\n <span class=\"pp-purchase-entry__date\">\n {{ formatDate(row.purchasedDate ?? row.createdDate) }}\n </span>\n @if (row.quantity) {\n <span class=\"pp-purchase-entry__qty\">{{ row.quantity }}</span>\n }\n </header>\n\n @if (displayPurchaseNote(row) !== '\u2014') {\n <p class=\"pp-purchase-entry__note\">{{ displayPurchaseNote(row) }}</p>\n }\n\n <section class=\"pp-comments pp-comments--nested\" aria-label=\"B\u00ECnh lu\u1EADn l\u1EA7n mua\">\n <h3 class=\"pp-comments__title\">\n B\u00ECnh lu\u1EADn\n <span class=\"pp-comments__count\">{{ commentsForRequest(row.requestId).length }}</span>\n </h3>\n\n <ul class=\"pp-comments__list\">\n @for (comment of commentsForRequest(row.requestId); track comment.id) {\n <li class=\"pp-comment\">\n <div class=\"pp-comment__head\">\n <span class=\"pp-comment__author\">{{ comment.createdBy || 'guest' }}</span>\n <span class=\"pp-comment__date\">{{ formatDate(comment.createdDate) }}</span>\n </div>\n <p class=\"pp-comment__text\">{{ comment.text }}</p>\n </li>\n } @empty {\n <li class=\"pp-comments__empty\">Ch\u01B0a c\u00F3 b\u00ECnh lu\u1EADn cho l\u1EA7n mua n\u00E0y.</li>\n }\n </ul>\n\n <div class=\"pp-comments__composer\">\n <textarea\n class=\"pp-comments__input\"\n rows=\"2\"\n [value]=\"historyCommentDraft(row.requestId)\"\n (input)=\"onHistoryCommentDraft(row.requestId, $any($event.target).value)\"\n [disabled]=\"isHistoryCommentSaving(row.requestId)\"\n placeholder=\"Th\u00EAm b\u00ECnh lu\u1EADn cho l\u1EA7n mua n\u00E0y\u2026\"\n [attr.aria-label]=\"'B\u00ECnh lu\u1EADn l\u1EA7n ' + (purchaseHistoryRows().length - idx)\"\n ></textarea>\n <button\n type=\"button\"\n class=\"pp-comments__send\"\n (click)=\"submitHistoryComment(row)\"\n [disabled]=\"isHistoryCommentSaving(row.requestId) || !historyCommentDraft(row.requestId).trim()\"\n title=\"G\u1EEDi b\u00ECnh lu\u1EADn\"\n aria-label=\"G\u1EEDi b\u00ECnh lu\u1EADn\"\n >\n @if (isHistoryCommentSaving(row.requestId)) {\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i>\n } @else {\n <i class=\"pi pi-send\" aria-hidden=\"true\"></i>\n }\n </button>\n </div>\n </section>\n </article>\n }\n </div>\n\n <footer class=\"pp-history-sheet__foot\">\n <button type=\"button\" class=\"pp-btn pp-btn--ghost\" (click)=\"closePurchaseDetail()\">\u0110\u00F3ng</button>\n <button\n type=\"button\"\n class=\"pp-btn pp-btn--primary\"\n (click)=\"duplicateFromPurchased(detailItem, $event); closePurchaseDetail()\"\n >\n Mua l\u1EA1i\n </button>\n </footer>\n </div>\n </div>\n}\n\n@if (deleteConfirm()) {\n <div class=\"pp-overlay\" (click)=\"cancelDelete()\" role=\"presentation\">\n <div\n class=\"pp-confirm\"\n role=\"alertdialog\"\n aria-modal=\"true\"\n aria-label=\"Xo\u00E1 m\u00F3n\"\n (click)=\"$event.stopPropagation()\"\n >\n <h2 class=\"pp-confirm__title\">Xo\u00E1 m\u00F3n n\u00E0y?</h2>\n <p class=\"pp-confirm__body\">{{ deleteConfirm()?.itemName }}</p>\n <div class=\"pp-confirm__foot\">\n <button type=\"button\" class=\"pp-btn pp-btn--ghost\" (click)=\"cancelDelete()\" [disabled]=\"saving()\">\n Hu\u1EF7\n </button>\n <button type=\"button\" class=\"pp-btn pp-btn--danger\" (click)=\"confirmDelete()\" [disabled]=\"saving()\">\n @if (saving()) {\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i>\n \u0110ang xo\u00E1\u2026\n } @else {\n Xo\u00E1\n }\n </button>\n </div>\n </div>\n </div>\n}\n", styles: [":host{--bg: #f6f7fb;--surface: #ffffff;--border: #e6e8ef;--primary: #5b6cff;--primary-soft: #eef0ff;--text: #0f172a;--muted: #475569;--muted-2: #94a3b8;--success-soft: #ecfdf5;--success: #059669;--warning-soft: #fff7ed;--warning: #ea580c;--danger: #e11d48;--danger-soft: #fff1f2;position:fixed;inset:0;z-index:200;background:var(--bg);display:flex;flex-direction:column;color:var(--text);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;animation:pp-slide .22s cubic-bezier(.22,1,.36,1)}@keyframes pp-slide{0%{transform:translate(8px);opacity:0}to{transform:translate(0);opacity:1}}.pp-root{display:flex;flex-direction:column;flex:1;min-height:0}.pp-header{flex-shrink:0;display:flex;align-items:center;gap:6px;padding:8px 10px;padding-top:calc(8px + env(safe-area-inset-top,0px));background:var(--surface);border-bottom:1px solid var(--border)}.pp-back{width:32px;height:32px;border:none;background:transparent;border-radius:9px;color:var(--muted);font-size:15px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;-webkit-tap-highlight-color:transparent}.pp-back:hover{background:var(--primary-soft);color:var(--primary)}.pp-header__title{margin:0;font-size:16px;font-weight:700;line-height:1.15}.pp-header__spacer{flex:1}.pp-icon-btn{width:32px;height:32px;border:none;background:transparent;border-radius:9px;color:var(--muted);font-size:15px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;-webkit-tap-highlight-color:transparent}.pp-icon-btn:hover{background:var(--primary-soft);color:var(--primary)}.pp-icon-btn--danger:hover{background:var(--danger-soft);color:var(--danger)}.pp-icon-btn--success{color:var(--success)}.pp-icon-btn--success:hover{background:var(--success-soft);color:var(--success)}.pp-icon-btn__spin{animation:pp-spin .8s linear infinite}@keyframes pp-spin{to{transform:rotate(360deg)}}.pp-banner{margin:10px 12px 0;padding:10px 12px;border-radius:12px;display:flex;align-items:center;gap:8px;font-size:13px}.pp-banner--error{background:var(--danger-soft);color:var(--danger)}.pp-banner--success{background:var(--success-soft);color:var(--success)}.pp-banner__close{margin-left:auto;border:none;background:transparent;color:inherit;cursor:pointer}.pp-overview{flex-shrink:0;display:grid;grid-template-columns:repeat(4,1fr);gap:4px;padding:10px 12px 0}.pp-overview__cell{background:var(--surface);border:1px solid var(--border);border-radius:0;padding:8px 4px;text-align:center}.pp-overview__value{display:block;font-size:16px;font-weight:700;line-height:1.1}.pp-overview__value--amber{color:var(--warning)}.pp-overview__value--danger{color:var(--danger)}.pp-overview__value--success{color:var(--success)}.pp-overview__label{display:block;margin-top:2px;font-size:8px;color:var(--muted-2);line-height:1.2}.pp-priority-bar{flex-shrink:0;padding:8px 12px 0}.pp-priority-bar__track{display:flex;height:5px;overflow:hidden;background:var(--border)}.pp-priority-bar__segment{height:100%}.pp-priority-bar__segment--high{background:var(--danger)}.pp-priority-bar__segment--medium{background:#eab308}.pp-priority-bar__segment--low{background:#22c55e}.pp-priority-bar__legend{display:flex;gap:10px;margin-top:4px;font-size:9px;color:var(--muted)}.pp-tabs{flex-shrink:0;display:flex;margin:10px 12px 0;border:1px solid var(--border);background:#f7f8fc}.pp-tabs__btn{flex:1;border:none;background:transparent;padding:8px 0;font-size:11px;font-weight:700;color:var(--muted);cursor:pointer;font-family:inherit}.pp-tabs__btn:first-child{border-right:1px solid var(--border)}.pp-tabs__btn--active{background:var(--surface);color:var(--text)}.pp-tabs__count{color:var(--muted-2);font-weight:700}.pp-filters{flex-shrink:0;display:flex;gap:4px;padding:8px 12px 0;overflow-x:auto}.pp-filter-chip{flex-shrink:0;border:1px solid var(--border);border-radius:0;background:var(--surface);color:var(--muted);font-size:10px;font-weight:600;padding:4px 8px;cursor:pointer;font-family:inherit}.pp-filter-chip--active{border-color:var(--primary);background:var(--primary-soft);color:var(--primary)}.pp-group-label{margin:4px 0 0;font-size:10px;font-weight:700;letter-spacing:.03em}.pp-group-label--high{color:var(--danger)}.pp-group-label--medium{color:#d97706}.pp-group-label--low{color:var(--success)}.pp-list{flex:1;overflow:auto;padding:8px 12px 12px;display:flex;flex-direction:column;gap:5px}.pp-priority{flex-shrink:0;width:18px;padding-top:2px;display:inline-flex;align-items:flex-start;justify-content:center;font-size:12px;font-weight:700}.pp-priority--high{color:#e11d48}.pp-priority--medium{color:#eab308}.pp-priority--low{color:#22c55e}.pp-priority--muted{opacity:.55}.pp-row{position:relative;display:flex;gap:6px;align-items:flex-start;padding:9px 10px;background:var(--surface);border:1px solid var(--border);border-radius:0}.pp-row--pending{border-left-width:3px}.pp-row--high{border-left-color:var(--danger)}.pp-row--medium{border-left-color:#eab308}.pp-row--low{border-left-color:#22c55e}.pp-row--purchased{opacity:.95;background:#fafbfc}.pp-row--clickable{cursor:pointer;transition:border-color .15s ease,background .15s ease}.pp-row--clickable:hover{border-color:color-mix(in srgb,var(--primary) 35%,var(--border));background:#f8fafc}.pp-row--clickable:focus-visible{outline:2px solid var(--primary);outline-offset:2px}.pp-row--menu-open{z-index:10;overflow:visible}.pp-row--clickable{cursor:pointer;transition:background .15s ease}.pp-row--clickable:hover{background:#f8fafc}.pp-row--clickable:focus-visible{outline:2px solid var(--primary);outline-offset:-2px}.pp-row__body{flex:1;min-width:0}.pp-row__top{display:flex;align-items:baseline;justify-content:space-between;gap:8px;margin-bottom:2px}.pp-row__main{flex:1;min-width:0}.pp-row__title{margin:0;font-size:14px;font-weight:700;line-height:1.25;min-width:0}.pp-row--compact .pp-row__title{font-size:14px}.pp-row__desc{margin:0 0 4px;font-size:12px;color:var(--muted);line-height:1.35;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.pp-row__qty{flex-shrink:0;font-size:12px;color:var(--primary);font-weight:700;white-space:nowrap}.pp-row__note{margin:0 0 4px;font-size:12px;color:var(--muted);line-height:1.35;display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden}.pp-row__meta{margin:4px 0 0;font-size:9px;color:var(--muted-2);min-width:0}.pp-row__comments{display:inline-flex;align-items:center;gap:3px;white-space:nowrap}.pp-row__comments i{font-size:9px}.pp-row__actions-col{display:flex;flex-direction:column;align-items:center;gap:6px;flex-shrink:0}.pp-row__purchase-note{margin:0 0 6px;font-size:13px;color:var(--success);line-height:1.4}.pp-row__purchase-note i{margin-right:4px;font-size:12px}.pp-row__confirm{flex-shrink:0;width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--success);border-radius:0;background:var(--success-soft);color:var(--success);font-size:12px;cursor:pointer;font-family:inherit;-webkit-tap-highlight-color:transparent}.pp-row__confirm:hover{background:var(--success);color:#fff}.pp-row__confirm:disabled{opacity:.6;cursor:not-allowed}.pp-row__menu-wrap{position:relative;flex-shrink:0;align-self:center}.pp-icon-btn--menu{color:var(--muted-2)}.pp-row-menu{position:absolute;top:calc(100% + 4px);right:0;min-width:148px;background:var(--surface);border:1px solid var(--border);border-radius:0;box-shadow:0 8px 24px #0f172a1f;padding:4px;z-index:20}.pp-row-menu__item{width:100%;border:none;background:transparent;border-radius:0;padding:8px 10px;display:inline-flex;align-items:center;gap:8px;font-size:12px;font-weight:600;color:var(--text);cursor:pointer;text-align:left;font-family:inherit}.pp-row-menu__item:not(:last-child){border-bottom:1px solid var(--border)}.pp-row-menu__item:hover{background:var(--primary-soft);color:var(--primary)}.pp-row-menu__item--danger{color:var(--danger)}.pp-row-menu__item--danger:hover{background:var(--danger-soft);color:var(--danger)}.pp-row-menu__badge{margin-left:auto;font-size:11px;font-weight:700;padding:2px 7px;border-radius:999px;background:var(--primary-soft);color:var(--primary)}.pp-empty{margin:12px 0;text-align:center;color:var(--muted-2);font-size:14px}.pp-empty--group{margin:8px 0 0;font-size:13px}.pp-add-wrap{flex-shrink:0;padding:12px;padding-bottom:calc(12px + env(safe-area-inset-bottom,0px));background:var(--surface);border-top:1px solid var(--border)}.pp-add{width:100%;border:none;border-radius:0;padding:11px 16px;background:var(--primary);color:#fff;font-size:15px;font-weight:700;display:inline-flex;align-items:center;justify-content:center;gap:8px;cursor:pointer}.pp-add:disabled{opacity:.6;cursor:not-allowed}.pp-overlay{position:fixed;inset:0;z-index:420;background:#0f172a80;display:flex;align-items:center;justify-content:center;padding:16px;padding-top:calc(16px + env(safe-area-inset-top,0px));padding-bottom:calc(16px + env(safe-area-inset-bottom,0px))}.pp-overlay--detail{background:#0f172a61;padding:12px}.pp-confirm{width:100%;max-width:360px;background:var(--surface);border-radius:0;padding:18px 16px}.pp-confirm__title{margin:0 0 8px;font-size:16px}.pp-confirm__body{margin:0 0 16px;color:var(--muted);font-size:14px}.pp-confirm__foot{display:flex;justify-content:flex-end;gap:8px}.pp-btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;border:none;border-radius:10px;padding:9px 16px;font-size:14px;font-weight:600;cursor:pointer}.pp-btn--ghost{background:transparent;color:var(--muted)}.pp-btn--danger{background:var(--danger);color:#fff}.pp-btn--primary{background:var(--primary);color:#fff}.pp-btn--success{background:var(--success-soft);border:1px solid var(--success);color:var(--success)}.pp-btn--block{width:100%}.pp-btn--danger-soft{background:color-mix(in srgb,var(--danger) 10%,var(--surface));border:1px solid color-mix(in srgb,var(--danger) 35%,var(--border));color:var(--danger)}.pp-icon-btn--soft{width:34px;height:34px;border-radius:10px;background:var(--surface-2, #f1f5f9);color:var(--muted)}.pp-detail{width:100%;max-width:420px;max-height:min(90vh,720px);background:var(--surface);border:1px solid var(--border);border-radius:16px;display:flex;flex-direction:column;overflow:hidden}.pp-detail__hero{padding:16px 16px 12px;border-bottom:1px solid color-mix(in srgb,var(--border) 70%,transparent)}.pp-detail__hero-top{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:10px}.pp-detail__priority{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:999px;font-size:11px;font-weight:700;background:var(--surface-2, #f1f5f9);color:var(--muted)}.pp-detail__priority--high{background:color-mix(in srgb,var(--danger) 12%,white);color:var(--danger)}.pp-detail__priority--medium{background:color-mix(in srgb,#d97706 12%,white);color:#d97706}.pp-detail__priority--low{background:color-mix(in srgb,var(--success) 12%,white);color:var(--success)}.pp-detail__priority .fa-solid{font-size:10px}.pp-detail__name{margin:0 0 8px;font-size:20px;font-weight:800;line-height:1.25;letter-spacing:-.02em;color:var(--text);overflow-wrap:anywhere}.pp-detail__meta{margin:0;display:flex;flex-wrap:wrap;gap:12px;font-size:12px;font-weight:600;color:var(--muted)}.pp-detail__meta span{display:inline-flex;align-items:center;gap:6px}.pp-detail__meta .fa-solid{font-size:11px;opacity:.75}.pp-detail__body{flex:1;overflow:auto;padding:14px 16px 16px;display:flex;flex-direction:column;gap:10px}.pp-detail__foot{display:flex;flex-direction:column;gap:8px;padding:12px 16px 16px;border-top:1px solid color-mix(in srgb,var(--border) 70%,transparent);background:color-mix(in srgb,var(--surface-2, #f8fafc) 50%,var(--surface))}.pp-detail__foot-row{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}.pp-detail__foot-row .pp-btn{width:100%;padding:9px 8px;font-size:13px}.pp-detail-card{padding:12px 14px;border-radius:14px;background:var(--surface-2, #f8fafc);border:1px solid color-mix(in srgb,var(--border) 80%,transparent)}.pp-detail-card__title{margin:0 0 8px;font-size:12px;font-weight:700;color:var(--muted)}.pp-detail-card__text{margin:0;font-size:13px;line-height:1.5;color:var(--text);overflow-wrap:anywhere}.pp-detail-kv{margin:0}.pp-detail-kv__row{display:flex;align-items:baseline;justify-content:space-between;gap:12px;padding:5px 0}.pp-detail-kv__row:not(:last-child){border-bottom:1px solid color-mix(in srgb,var(--border) 55%,transparent)}.pp-detail-kv__row dt{margin:0;font-size:12px;font-weight:500;color:var(--muted)}.pp-detail-kv__row dd{margin:0;font-size:13px;font-weight:700;color:var(--text);text-align:right;overflow-wrap:anywhere}.pp-detail-kv__value--overdue{color:var(--danger)}.pp-comments{margin-top:4px}.pp-comments__head{display:flex;align-items:center;gap:8px;margin-bottom:10px}.pp-comments__title{margin:0;font-size:13px;font-weight:700;color:var(--text)}.pp-comments__count{font-size:11px;font-weight:700;min-width:22px;height:22px;padding:0 7px;display:inline-flex;align-items:center;justify-content:center;border-radius:999px;background:var(--primary-soft);color:var(--primary)}.pp-comments__list{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:8px}.pp-comments__empty{font-size:12px;line-height:1.45;color:var(--muted-2);padding:10px 12px;border-radius:12px;background:var(--surface-2, #f8fafc);border:1px dashed color-mix(in srgb,var(--border) 90%,transparent)}.pp-comments__composer{display:flex;align-items:flex-end;gap:8px;margin-top:12px}.pp-comments__input{flex:1;min-width:0;resize:vertical;min-height:44px;padding:10px 12px;border:1px solid color-mix(in srgb,var(--border) 90%,transparent);border-radius:12px;background:var(--surface);color:var(--text);font-size:13px;font-family:inherit;outline:none;line-height:1.4}.pp-comments__input:focus{border-color:var(--primary)}.pp-comments__send{flex-shrink:0;width:40px;height:40px;display:inline-flex;align-items:center;justify-content:center;border:none;border-radius:12px;background:var(--primary);color:#fff;font-size:14px;cursor:pointer;font-family:inherit;-webkit-tap-highlight-color:transparent}.pp-comments__send:hover:not(:disabled){filter:brightness(1.05)}.pp-comments__send:disabled{opacity:.5;cursor:not-allowed}.pp-comment{border-radius:12px;background:var(--surface-2, #f8fafc);border:1px solid color-mix(in srgb,var(--border) 80%,transparent);padding:10px 12px}.pp-comment__head{display:flex;align-items:baseline;justify-content:space-between;gap:8px;margin-bottom:6px}.pp-comment__author{font-size:12px;font-weight:700;color:var(--text)}.pp-comment__date{font-size:10px;font-weight:600;color:var(--muted-2);font-variant-numeric:tabular-nums}.pp-comment__text{margin:0;font-size:13px;color:var(--muted);line-height:1.45;overflow-wrap:anywhere;white-space:pre-wrap}.pp-comment__link{color:var(--primary);text-decoration:underline;text-underline-offset:2px;word-break:break-all}.pp-comment__link:hover{filter:brightness(.92)}.pp-purchase-entry{border:1px solid var(--border);background:#fafbfc;padding:12px;margin-bottom:10px}.pp-purchase-entry:last-child{margin-bottom:0}.pp-purchase-entry__head{display:flex;flex-wrap:wrap;align-items:baseline;gap:6px 10px;margin-bottom:8px}.pp-purchase-entry__index{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.03em;color:var(--primary)}.pp-purchase-entry__date{font-size:13px;font-weight:700;color:var(--text)}.pp-purchase-entry__qty{margin-left:auto;font-size:12px;font-weight:700;color:var(--muted)}.pp-purchase-entry__note{margin:0 0 10px;font-size:12px;line-height:1.4;color:var(--muted);overflow-wrap:anywhere}.pp-comments--nested{margin-top:0;padding-top:0;border-top:none}.pp-history-sheet{width:min(100%,520px);max-height:min(92vh,720px);margin:0 auto;background:var(--surface);border-radius:0;display:flex;flex-direction:column;overflow:hidden}.pp-history-sheet__head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding:16px;border-bottom:1px solid var(--border)}.pp-history-sheet__title{margin:0;font-size:17px;font-weight:700}.pp-history-sheet__sub{margin:4px 0 0;font-size:13px;color:var(--muted)}.pp-history-sheet__body{flex:1;overflow:auto;padding:12px 16px}.pp-history-sheet__foot{display:flex;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid var(--border)}.pp-history-table{width:100%;border-collapse:collapse;font-size:13px}.pp-history-table th,.pp-history-table td{padding:10px 8px;text-align:left;border-bottom:1px solid var(--border);vertical-align:top}.pp-history-table th{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.03em;color:var(--muted-2);background:#f8fafc;position:sticky;top:0}.pp-history-table tbody tr:last-child td{border-bottom:none}.pp-history-table td:first-child{width:36px;color:var(--muted-2);font-weight:700}.pp-history-table td:nth-child(3){white-space:nowrap}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: PreparingPurchaseDialogComponent, selector: "app-preparing-purchase-dialog", inputs: ["mode", "heading", "products", "initialProductId", "initialProductName", "initialProductDescription", "initialAmount", "initialNote", "initialAssignee", "initialDueDate", "initialPriority", "saving"], outputs: ["cancel", "save"] }, { kind: "component", type: PreparingPurchaseConfirmDialogComponent, selector: "app-preparing-purchase-confirm-dialog", inputs: ["itemName", "quantity", "saving"], outputs: ["cancel", "confirm"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
23453
23743
|
}
|
|
23454
23744
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: PreparingPurchaseComponent, decorators: [{
|
|
23455
23745
|
type: Component,
|
|
@@ -23457,7 +23747,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
23457
23747
|
CommonModule,
|
|
23458
23748
|
PreparingPurchaseDialogComponent,
|
|
23459
23749
|
PreparingPurchaseConfirmDialogComponent,
|
|
23460
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section class=\"pp-root\" role=\"dialog\" aria-modal=\"true\" aria-label=\"C\u1EA7n mua s\u1EAFm\">\n <header class=\"pp-header\">\n <button type=\"button\" class=\"pp-back\" (click)=\"back.emit()\" aria-label=\"Quay l\u1EA1i\">\n <i class=\"pi pi-chevron-left\" aria-hidden=\"true\"></i>\n </button>\n <h1 class=\"pp-header__title\">C\u1EA7n mua s\u1EAFm</h1>\n <span class=\"pp-header__spacer\"></span>\n <button\n type=\"button\"\n class=\"pp-icon-btn\"\n (click)=\"load()\"\n [disabled]=\"loading()\"\n title=\"L\u00E0m m\u1EDBi danh s\u00E1ch\"\n aria-label=\"L\u00E0m m\u1EDBi danh s\u00E1ch\"\n >\n <i class=\"pi pi-refresh\" [class.pp-icon-btn__spin]=\"loading()\" aria-hidden=\"true\"></i>\n </button>\n </header>\n\n @if (errorMsg()) {\n <div class=\"pp-banner pp-banner--error\" role=\"alert\">\n <span>{{ errorMsg() }}</span>\n <button type=\"button\" class=\"pp-banner__close\" (click)=\"clearMessages()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\n </button>\n </div>\n }\n\n @if (successMsg()) {\n <div class=\"pp-banner pp-banner--success\" role=\"status\">\n <span>{{ successMsg() }}</span>\n <button type=\"button\" class=\"pp-banner__close\" (click)=\"clearMessages()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\n </button>\n </div>\n }\n\n <div class=\"pp-overview\" aria-label=\"T\u1ED5ng quan\">\n <div class=\"pp-overview__cell\">\n <span class=\"pp-overview__value pp-overview__value--amber\">{{ pendingItems().length }}</span>\n <span class=\"pp-overview__label\">Ch\u01B0a mua</span>\n </div>\n <div class=\"pp-overview__cell\">\n <span class=\"pp-overview__value pp-overview__value--danger\">{{ highPriorityCount() }}</span>\n <span class=\"pp-overview__label\">\u01AFu ti\u00EAn cao</span>\n </div>\n <div class=\"pp-overview__cell\">\n <span class=\"pp-overview__value pp-overview__value--danger\">{{ overdueCount() }}</span>\n <span class=\"pp-overview__label\">Qu\u00E1 h\u1EA1n</span>\n </div>\n <div class=\"pp-overview__cell\">\n <span class=\"pp-overview__value pp-overview__value--success\">{{ purchasedThisMonthCount() }}</span>\n <span class=\"pp-overview__label\">\u0110\u00E3 mua th\u00E1ng</span>\n </div>\n </div>\n\n @if (pendingItems().length > 0) {\n <div class=\"pp-priority-bar\" aria-hidden=\"true\">\n <div class=\"pp-priority-bar__track\">\n @for (segment of prioritySegments(); track segment.priority) {\n <span\n class=\"pp-priority-bar__segment\"\n [class.pp-priority-bar__segment--high]=\"segment.priority === 'high'\"\n [class.pp-priority-bar__segment--medium]=\"segment.priority === 'medium'\"\n [class.pp-priority-bar__segment--low]=\"segment.priority === 'low'\"\n [style.width.%]=\"segment.percent\"\n ></span>\n }\n </div>\n <div class=\"pp-priority-bar__legend\">\n <span>\u2191 Cao {{ priorityCounts().high }}</span>\n <span>\u2014 TB {{ priorityCounts().medium }}</span>\n <span>\u2193 Th\u1EA5p {{ priorityCounts().low }}</span>\n </div>\n </div>\n }\n\n <div class=\"pp-tabs\" role=\"tablist\" aria-label=\"Tr\u1EA1ng th\u00E1i mua\">\n <button\n type=\"button\"\n class=\"pp-tabs__btn\"\n role=\"tab\"\n [class.pp-tabs__btn--active]=\"activeTab() === 'pending'\"\n [attr.aria-selected]=\"activeTab() === 'pending'\"\n (click)=\"setTab('pending')\"\n >\n C\u1EA7n mua <span class=\"pp-tabs__count\">{{ pendingItems().length }}</span>\n </button>\n <button\n type=\"button\"\n class=\"pp-tabs__btn\"\n role=\"tab\"\n [class.pp-tabs__btn--active]=\"activeTab() === 'purchased'\"\n [attr.aria-selected]=\"activeTab() === 'purchased'\"\n (click)=\"setTab('purchased')\"\n >\n \u0110\u00E3 mua <span class=\"pp-tabs__count\">{{ purchasedItems().length }}</span>\n </button>\n </div>\n\n @if (activeTab() === 'pending') {\n <div class=\"pp-filters\" role=\"group\" aria-label=\"L\u1ECDc theo \u01B0u ti\u00EAn\">\n @for (chip of priorityFilterOptions; track chip.id) {\n <button\n type=\"button\"\n class=\"pp-filter-chip\"\n [class.pp-filter-chip--active]=\"priorityFilter() === chip.id\"\n (click)=\"setPriorityFilter(chip.id)\"\n >\n {{ chip.label }}\n </button>\n }\n </div>\n }\n\n <div class=\"pp-list\">\n @if (activeTab() === 'pending') {\n @if (priorityFilter() === 'all') {\n @for (group of pendingPriorityGroups(); track group.priority) {\n @if (group.items.length > 0) {\n <p\n class=\"pp-group-label\"\n [class.pp-group-label--high]=\"group.priority === 'high'\"\n [class.pp-group-label--medium]=\"group.priority === 'medium'\"\n [class.pp-group-label--low]=\"group.priority === 'low'\"\n >\n {{ priorityGroupHeading(group.priority) }}\n </p>\n @for (item of group.items; track itemKey(item)) {\n <ng-container *ngTemplateOutlet=\"pendingRow; context: { $implicit: item }\" />\n }\n }\n }\n } @else {\n <p\n class=\"pp-group-label\"\n [class.pp-group-label--high]=\"priorityFilter() === 'high'\"\n [class.pp-group-label--medium]=\"priorityFilter() === 'medium'\"\n [class.pp-group-label--low]=\"priorityFilter() === 'low'\"\n >\n {{ filteredPriorityHeading() }}\n </p>\n @for (item of filteredPendingItems(); track itemKey(item)) {\n <ng-container *ngTemplateOutlet=\"pendingRow; context: { $implicit: item }\" />\n }\n }\n\n @if (filteredPendingItems().length === 0 && !loading()) {\n <p class=\"pp-empty\">Kh\u00F4ng c\u00F2n m\u00F3n n\u00E0o c\u1EA7n mua.</p>\n }\n } @else {\n @for (item of purchasedItems(); track itemKey(item)) {\n <ng-container *ngTemplateOutlet=\"purchasedRow; context: { $implicit: item }\" />\n } @empty {\n @if (!loading()) {\n <p class=\"pp-empty\">Ch\u01B0a c\u00F3 m\u00F3n n\u00E0o \u0111\u00E3 mua.</p>\n }\n }\n }\n\n @if (loading() && pendingItems().length === 0 && purchasedItems().length === 0) {\n <p class=\"pp-empty\">\u0110ang t\u1EA3i\u2026</p>\n }\n </div>\n\n <div class=\"pp-add-wrap\">\n <button type=\"button\" class=\"pp-add\" (click)=\"openAdd()\" [disabled]=\"saving()\">\n <i class=\"pi pi-plus\" aria-hidden=\"true\"></i>\n <span>Th\u00EAm m\u00F3n</span>\n </button>\n </div>\n</section>\n\n<ng-template #pendingRow let-item>\n <article\n class=\"pp-row pp-row--pending pp-row--clickable\"\n [class.pp-row--menu-open]=\"rowMenuKey() === itemKey(item)\"\n [class.pp-row--high]=\"item.priority === 'high'\"\n [class.pp-row--medium]=\"item.priority === 'medium'\"\n [class.pp-row--low]=\"item.priority === 'low'\"\n (click)=\"openPendingDetailItem(item)\"\n role=\"button\"\n tabindex=\"0\"\n (keydown.enter)=\"openPendingDetailItem(item)\"\n (keydown.space)=\"$event.preventDefault(); openPendingDetailItem(item)\"\n [attr.aria-label]=\"'Xem chi ti\u1EBFt ' + item.itemName\"\n >\n <span\n class=\"pp-priority\"\n [class.pp-priority--high]=\"item.priority === 'high'\"\n [class.pp-priority--medium]=\"item.priority === 'medium'\"\n [class.pp-priority--low]=\"item.priority === 'low'\"\n [attr.title]=\"'\u01AFu ti\u00EAn ' + priorityLabel(item.priority)\"\n >\n <i class=\"fa-solid\" [ngClass]=\"priorityIcon(item.priority)\" aria-hidden=\"true\"></i>\n </span>\n\n <div class=\"pp-row__body\">\n <div class=\"pp-row__top\">\n <h2 class=\"pp-row__title\">{{ item.itemName }}</h2>\n @if (item.quantity) {\n <span class=\"pp-row__qty\">{{ item.quantity }}</span>\n }\n </div>\n @if (rowSubtitle(item); as subtitle) {\n <p class=\"pp-row__desc\">{{ subtitle }}</p>\n }\n <p class=\"pp-row__meta\">\n Th\u00EAm {{ formatDate(item.createdDate) }}\n @if (item.createdBy) {\n \u00B7 {{ item.createdBy }}\n }\n <span class=\"pp-row__comments\" [attr.title]=\"commentCountForRequest(item.requestId) + ' b\u00ECnh lu\u1EADn'\">\n \u00B7 <i class=\"pi pi-comment\" aria-hidden=\"true\"></i>\n {{ commentCountForRequest(item.requestId) }}\n </span>\n </p>\n </div>\n\n <div class=\"pp-row__actions-col\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"pp-row__confirm\"\n (click)=\"openPurchaseConfirm(item)\"\n [disabled]=\"saving()\"\n title=\"\u0110\u00E1nh d\u1EA5u \u0111\u00E3 mua\"\n aria-label=\"\u0110\u00E1nh d\u1EA5u \u0111\u00E3 mua\"\n >\n <i class=\"pi pi-check\" aria-hidden=\"true\"></i>\n </button>\n <div class=\"pp-row__menu-wrap\">\n <button\n type=\"button\"\n class=\"pp-icon-btn pp-icon-btn--menu\"\n (click)=\"toggleRowMenu(item, $event)\"\n title=\"Tu\u1EF3 ch\u1ECDn\"\n aria-label=\"Tu\u1EF3 ch\u1ECDn\"\n aria-haspopup=\"menu\"\n [attr.aria-expanded]=\"rowMenuKey() === itemKey(item)\"\n >\n <i class=\"pi pi-ellipsis-v\" aria-hidden=\"true\"></i>\n </button>\n @if (rowMenuKey() === itemKey(item)) {\n <div class=\"pp-row-menu\" role=\"menu\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"pp-row-menu__item\"\n role=\"menuitem\"\n (click)=\"openPendingDetail(item, $event)\"\n >\n Xem chi ti\u1EBFt\n </button>\n <button\n type=\"button\"\n class=\"pp-row-menu__item\"\n role=\"menuitem\"\n (click)=\"openEdit(item); rowMenuKey.set(null)\"\n >\n S\u1EEDa\n </button>\n <button\n type=\"button\"\n class=\"pp-row-menu__item pp-row-menu__item--danger\"\n role=\"menuitem\"\n (click)=\"askDelete(item, $event)\"\n >\n Xo\u00E1\n </button>\n </div>\n }\n </div>\n </div>\n </article>\n</ng-template>\n\n<ng-template #purchasedRow let-item>\n <article\n class=\"pp-row pp-row--purchased pp-row--clickable\"\n [class.pp-row--menu-open]=\"rowMenuKey() === itemKey(item)\"\n (click)=\"openPurchaseDetail(item, $event)\"\n role=\"button\"\n tabindex=\"0\"\n (keydown.enter)=\"openPurchaseDetail(item, $event)\"\n (keydown.space)=\"$event.preventDefault(); openPurchaseDetailItem(item)\"\n [attr.aria-label]=\"'Xem l\u1ECBch s\u1EED mua ' + item.itemName\"\n >\n <span\n class=\"pp-priority pp-priority--muted\"\n [class.pp-priority--high]=\"item.priority === 'high'\"\n [class.pp-priority--medium]=\"item.priority === 'medium'\"\n [class.pp-priority--low]=\"item.priority === 'low'\"\n [attr.title]=\"'\u01AFu ti\u00EAn ' + priorityLabel(item.priority)\"\n >\n <i class=\"fa-solid\" [ngClass]=\"priorityIcon(item.priority)\" aria-hidden=\"true\"></i>\n </span>\n\n <div class=\"pp-row__body\">\n <div class=\"pp-row__top\">\n <h2 class=\"pp-row__title\">{{ item.itemName }}</h2>\n @if (item.quantity) {\n <span class=\"pp-row__qty\">{{ item.quantity }}</span>\n }\n </div>\n <p class=\"pp-row__desc\">\n @if (item.description) {\n {{ item.description }}\n } @else if (item.purchasedDate) {\n Mua {{ formatDate(item.purchasedDate) }}\n }\n </p>\n <p class=\"pp-row__meta\">\n @if (item.purchasedDate) {\n \u0110\u00E3 mua {{ formatDate(item.purchasedDate) }}\n }\n \u00B7 {{ purchaseCountForProduct(item.productId) }} l\u1EA7n\n <span class=\"pp-row__comments\" [attr.title]=\"commentCountForRequest(item.requestId) + ' b\u00ECnh lu\u1EADn'\">\n \u00B7 <i class=\"pi pi-comment\" aria-hidden=\"true\"></i>\n {{ commentCountForRequest(item.requestId) }}\n </span>\n </p>\n </div>\n\n <div class=\"pp-row__menu-wrap\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"pp-icon-btn pp-icon-btn--menu\"\n (click)=\"toggleRowMenu(item, $event)\"\n title=\"Tu\u1EF3 ch\u1ECDn\"\n aria-label=\"Tu\u1EF3 ch\u1ECDn\"\n aria-haspopup=\"menu\"\n [attr.aria-expanded]=\"rowMenuKey() === itemKey(item)\"\n >\n <i class=\"pi pi-ellipsis-v\" aria-hidden=\"true\"></i>\n </button>\n @if (rowMenuKey() === itemKey(item)) {\n <div class=\"pp-row-menu\" role=\"menu\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"pp-row-menu__item\"\n role=\"menuitem\"\n (click)=\"openPurchaseDetail(item, $event)\"\n >\n Xem chi ti\u1EBFt\n <span class=\"pp-row-menu__badge\">{{ purchaseCountForProduct(item.productId) }}</span>\n </button>\n <button\n type=\"button\"\n class=\"pp-row-menu__item\"\n role=\"menuitem\"\n (click)=\"duplicateFromPurchased(item, $event)\"\n >\n Mua l\u1EA1i\n </button>\n </div>\n }\n </div>\n </article>\n</ng-template>\n\n@if (isDialogOpen()) {\n <app-preparing-purchase-dialog\n [heading]=\"dialogHeading()\"\n [mode]=\"dialogMode()\"\n [products]=\"products()\"\n [initialProductId]=\"dialogProductId()\"\n [initialProductName]=\"dialogProductName()\"\n [initialProductDescription]=\"dialogProductDescription()\"\n [initialAmount]=\"dialogAmount()\"\n [initialNote]=\"dialogNote()\"\n [initialAssignee]=\"dialogAssignee()\"\n [initialDueDate]=\"dialogDueDate()\"\n [initialPriority]=\"dialogPriority()\"\n [saving]=\"saving()\"\n (cancel)=\"closeDialog()\"\n (save)=\"onSave($event)\"\n />\n}\n\n@if (purchaseConfirm()) {\n <app-preparing-purchase-confirm-dialog\n [itemName]=\"purchaseConfirm()?.itemName ?? ''\"\n [quantity]=\"purchaseConfirm()?.quantity ?? ''\"\n [saving]=\"saving()\"\n (cancel)=\"cancelPurchaseConfirm()\"\n (confirm)=\"onConfirmPurchase($event)\"\n />\n}\n\n@if (pendingDetailItem(); as detail) {\n <div class=\"pp-overlay pp-overlay--detail\" (click)=\"closePendingDetail()\" role=\"presentation\">\n <div\n class=\"pp-detail\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"detail.itemName\"\n (click)=\"$event.stopPropagation()\"\n >\n <header class=\"pp-detail__hero\">\n <div class=\"pp-detail__hero-top\">\n <span\n class=\"pp-detail__priority\"\n [class.pp-detail__priority--high]=\"detail.priority === 'high'\"\n [class.pp-detail__priority--medium]=\"detail.priority === 'medium'\"\n [class.pp-detail__priority--low]=\"detail.priority === 'low'\"\n >\n <i class=\"fa-solid\" [ngClass]=\"priorityIcon(detail.priority)\" aria-hidden=\"true\"></i>\n {{ priorityLabel(detail.priority) }}\n </span>\n <button type=\"button\" class=\"pp-icon-btn pp-icon-btn--soft\" (click)=\"closePendingDetail()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"fa-solid fa-xmark\" aria-hidden=\"true\"></i>\n </button>\n </div>\n <h2 class=\"pp-detail__name\">{{ detail.itemName }}</h2>\n <p class=\"pp-detail__meta\">\n <span>\n <i class=\"fa-solid fa-user\" aria-hidden=\"true\"></i>\n {{ detail.createdBy || 'guest' }}\n </span>\n <span>\n <i class=\"fa-solid fa-clock\" aria-hidden=\"true\"></i>\n {{ formatDateTime(detail.createdDate) }}\n </span>\n </p>\n </header>\n\n <div class=\"pp-detail__body\">\n <section class=\"pp-detail-card\" aria-label=\"Y\u00EAu c\u1EA7u mua\">\n <h3 class=\"pp-detail-card__title\">Y\u00EAu c\u1EA7u mua</h3>\n <dl class=\"pp-detail-kv\">\n <div class=\"pp-detail-kv__row\">\n <dt>S\u1ED1 l\u01B0\u1EE3ng</dt>\n <dd>{{ detail.quantity || '\u2014' }}</dd>\n </div>\n <div class=\"pp-detail-kv__row\">\n <dt>Ng\u01B0\u1EDDi ph\u1EE5 tr\u00E1ch</dt>\n <dd>{{ itemAssignee(detail) || '\u2014' }}</dd>\n </div>\n <div class=\"pp-detail-kv__row\">\n <dt>Th\u1EDDi h\u1EA1n mua</dt>\n <dd [class.pp-detail-kv__value--overdue]=\"isItemOverdue(detail)\">\n {{ itemDueDate(detail) || '\u2014' }}\n </dd>\n </div>\n </dl>\n </section>\n\n @if (detail.description?.trim()) {\n <section class=\"pp-detail-card\" aria-label=\"M\u00F4 t\u1EA3 s\u1EA3n ph\u1EA9m\">\n <h3 class=\"pp-detail-card__title\">M\u00F4 t\u1EA3</h3>\n <p class=\"pp-detail-card__text\">{{ detail.description }}</p>\n </section>\n }\n\n @if (itemNoteText(detail); as noteText) {\n <section class=\"pp-detail-card\" aria-label=\"Ghi ch\u00FA\">\n <h3 class=\"pp-detail-card__title\">Ghi ch\u00FA</h3>\n <p class=\"pp-detail-card__text\">{{ noteText }}</p>\n </section>\n }\n\n <section class=\"pp-comments\" aria-label=\"B\u00ECnh lu\u1EADn\">\n <div class=\"pp-comments__head\">\n <h3 class=\"pp-comments__title\">B\u00ECnh lu\u1EADn</h3>\n <span class=\"pp-comments__count\">{{ detailComments().length }}</span>\n </div>\n\n <ul class=\"pp-comments__list\">\n @for (comment of detailComments(); track comment.id) {\n <li class=\"pp-comment\">\n <div class=\"pp-comment__head\">\n <span class=\"pp-comment__author\">{{ comment.createdBy || 'guest' }}</span>\n <span class=\"pp-comment__date\">{{ formatDateTime(comment.createdDate) }}</span>\n </div>\n <p class=\"pp-comment__text\">\n @for (part of commentSegments(comment.text); track $index) {\n @if (part.type === 'link') {\n <a\n class=\"pp-comment__link\"\n [href]=\"part.href\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n (click)=\"$event.stopPropagation()\"\n >{{ part.value }}</a>\n } @else {\n {{ part.value }}\n }\n }\n </p>\n </li>\n } @empty {\n <li class=\"pp-comments__empty\">Ch\u01B0a c\u00F3 b\u00ECnh lu\u1EADn \u2014 th\u00EAm ghi ch\u00FA cho m\u1ECDi ng\u01B0\u1EDDi c\u00F9ng xem.</li>\n }\n </ul>\n\n <div class=\"pp-comments__composer\">\n <textarea\n class=\"pp-comments__input\"\n rows=\"2\"\n [value]=\"commentDraft()\"\n (input)=\"onCommentDraft($any($event.target).value)\"\n [disabled]=\"commentSaving()\"\n placeholder=\"Th\u00EAm b\u00ECnh lu\u1EADn\u2026\"\n aria-label=\"N\u1ED9i dung b\u00ECnh lu\u1EADn\"\n ></textarea>\n <button\n type=\"button\"\n class=\"pp-comments__send\"\n (click)=\"submitComment()\"\n [disabled]=\"commentSaving() || !commentDraft().trim()\"\n title=\"G\u1EEDi b\u00ECnh lu\u1EADn\"\n aria-label=\"G\u1EEDi b\u00ECnh lu\u1EADn\"\n >\n @if (commentSaving()) {\n <i class=\"fa-solid fa-spinner fa-spin\" aria-hidden=\"true\"></i>\n } @else {\n <i class=\"fa-solid fa-paper-plane\" aria-hidden=\"true\"></i>\n }\n </button>\n </div>\n </section>\n </div>\n\n <footer class=\"pp-detail__foot\">\n <button\n type=\"button\"\n class=\"pp-btn pp-btn--success pp-btn--block\"\n (click)=\"openPurchaseConfirm(detail); closePendingDetail()\"\n [disabled]=\"saving()\"\n >\n <i class=\"fa-solid fa-check\" aria-hidden=\"true\"></i>\n \u0110\u00E3 mua\n </button>\n <div class=\"pp-detail__foot-row\">\n <button\n type=\"button\"\n class=\"pp-btn pp-btn--ghost\"\n (click)=\"openEdit(detail); closePendingDetail()\"\n [disabled]=\"saving()\"\n >\n <i class=\"fa-solid fa-pencil\" aria-hidden=\"true\"></i>\n S\u1EEDa\n </button>\n <button\n type=\"button\"\n class=\"pp-btn pp-btn--danger-soft\"\n (click)=\"askDeleteFromDetail(detail)\"\n [disabled]=\"saving()\"\n >\n <i class=\"fa-solid fa-trash\" aria-hidden=\"true\"></i>\n X\u00F3a\n </button>\n <button type=\"button\" class=\"pp-btn pp-btn--ghost\" (click)=\"closePendingDetail()\">\n \u0110\u00F3ng\n </button>\n </div>\n </footer>\n </div>\n </div>\n}\n\n@if (purchaseDetailProductId() && purchaseDetailItem(); as detailItem) {\n <div class=\"pp-overlay\" (click)=\"closePurchaseDetail()\" role=\"presentation\">\n <div\n class=\"pp-history-sheet\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"'L\u1ECBch s\u1EED mua ' + detailItem.itemName\"\n (click)=\"$event.stopPropagation()\"\n >\n <header class=\"pp-history-sheet__head\">\n <div>\n <h2 class=\"pp-history-sheet__title\">{{ detailItem.itemName }}</h2>\n <p class=\"pp-history-sheet__sub\">\n \u0110\u00E3 mua {{ purchaseHistoryRows().length }} l\u1EA7n\n </p>\n </div>\n <button type=\"button\" class=\"pp-icon-btn\" (click)=\"closePurchaseDetail()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\n </button>\n </header>\n\n <div class=\"pp-history-sheet__body\">\n @for (row of purchaseHistoryRows(); track itemKey(row); let idx = $index) {\n <article class=\"pp-purchase-entry\">\n <header class=\"pp-purchase-entry__head\">\n <span class=\"pp-purchase-entry__index\">L\u1EA7n {{ purchaseHistoryRows().length - idx }}</span>\n <span class=\"pp-purchase-entry__date\">\n {{ formatDate(row.purchasedDate ?? row.createdDate) }}\n </span>\n @if (row.quantity) {\n <span class=\"pp-purchase-entry__qty\">{{ row.quantity }}</span>\n }\n </header>\n\n @if (displayPurchaseNote(row) !== '\u2014') {\n <p class=\"pp-purchase-entry__note\">{{ displayPurchaseNote(row) }}</p>\n }\n\n <section class=\"pp-comments pp-comments--nested\" aria-label=\"B\u00ECnh lu\u1EADn l\u1EA7n mua\">\n <h3 class=\"pp-comments__title\">\n B\u00ECnh lu\u1EADn\n <span class=\"pp-comments__count\">{{ commentsForRequest(row.requestId).length }}</span>\n </h3>\n\n <ul class=\"pp-comments__list\">\n @for (comment of commentsForRequest(row.requestId); track comment.id) {\n <li class=\"pp-comment\">\n <div class=\"pp-comment__head\">\n <span class=\"pp-comment__author\">{{ comment.createdBy || 'guest' }}</span>\n <span class=\"pp-comment__date\">{{ formatDate(comment.createdDate) }}</span>\n </div>\n <p class=\"pp-comment__text\">{{ comment.text }}</p>\n </li>\n } @empty {\n <li class=\"pp-comments__empty\">Ch\u01B0a c\u00F3 b\u00ECnh lu\u1EADn cho l\u1EA7n mua n\u00E0y.</li>\n }\n </ul>\n\n <div class=\"pp-comments__composer\">\n <textarea\n class=\"pp-comments__input\"\n rows=\"2\"\n [value]=\"historyCommentDraft(row.requestId)\"\n (input)=\"onHistoryCommentDraft(row.requestId, $any($event.target).value)\"\n [disabled]=\"isHistoryCommentSaving(row.requestId)\"\n placeholder=\"Th\u00EAm b\u00ECnh lu\u1EADn cho l\u1EA7n mua n\u00E0y\u2026\"\n [attr.aria-label]=\"'B\u00ECnh lu\u1EADn l\u1EA7n ' + (purchaseHistoryRows().length - idx)\"\n ></textarea>\n <button\n type=\"button\"\n class=\"pp-comments__send\"\n (click)=\"submitHistoryComment(row)\"\n [disabled]=\"isHistoryCommentSaving(row.requestId) || !historyCommentDraft(row.requestId).trim()\"\n title=\"G\u1EEDi b\u00ECnh lu\u1EADn\"\n aria-label=\"G\u1EEDi b\u00ECnh lu\u1EADn\"\n >\n @if (isHistoryCommentSaving(row.requestId)) {\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i>\n } @else {\n <i class=\"pi pi-send\" aria-hidden=\"true\"></i>\n }\n </button>\n </div>\n </section>\n </article>\n }\n </div>\n\n <footer class=\"pp-history-sheet__foot\">\n <button type=\"button\" class=\"pp-btn pp-btn--ghost\" (click)=\"closePurchaseDetail()\">\u0110\u00F3ng</button>\n <button\n type=\"button\"\n class=\"pp-btn pp-btn--primary\"\n (click)=\"duplicateFromPurchased(detailItem, $event); closePurchaseDetail()\"\n >\n Mua l\u1EA1i\n </button>\n </footer>\n </div>\n </div>\n}\n\n@if (deleteConfirm()) {\n <div class=\"pp-overlay\" (click)=\"cancelDelete()\" role=\"presentation\">\n <div\n class=\"pp-confirm\"\n role=\"alertdialog\"\n aria-modal=\"true\"\n aria-label=\"Xo\u00E1 m\u00F3n\"\n (click)=\"$event.stopPropagation()\"\n >\n <h2 class=\"pp-confirm__title\">Xo\u00E1 m\u00F3n n\u00E0y?</h2>\n <p class=\"pp-confirm__body\">{{ deleteConfirm()?.itemName }}</p>\n <div class=\"pp-confirm__foot\">\n <button type=\"button\" class=\"pp-btn pp-btn--ghost\" (click)=\"cancelDelete()\" [disabled]=\"saving()\">\n Hu\u1EF7\n </button>\n <button type=\"button\" class=\"pp-btn pp-btn--danger\" (click)=\"confirmDelete()\" [disabled]=\"saving()\">\n @if (saving()) {\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i>\n \u0110ang xo\u00E1\u2026\n } @else {\n Xo\u00E1\n }\n </button>\n </div>\n </div>\n </div>\n}\n", styles: [":host{--bg: #f6f7fb;--surface: #ffffff;--border: #e6e8ef;--primary: #5b6cff;--primary-soft: #eef0ff;--text: #0f172a;--muted: #475569;--muted-2: #94a3b8;--success-soft: #ecfdf5;--success: #059669;--warning-soft: #fff7ed;--warning: #ea580c;--danger: #e11d48;--danger-soft: #fff1f2;position:fixed;inset:0;z-index:200;background:var(--bg);display:flex;flex-direction:column;color:var(--text);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;animation:pp-slide .22s cubic-bezier(.22,1,.36,1)}@keyframes pp-slide{0%{transform:translate(8px);opacity:0}to{transform:translate(0);opacity:1}}.pp-root{display:flex;flex-direction:column;flex:1;min-height:0}.pp-header{flex-shrink:0;display:flex;align-items:center;gap:6px;padding:8px 10px;padding-top:calc(8px + env(safe-area-inset-top,0px));background:var(--surface);border-bottom:1px solid var(--border)}.pp-back{width:32px;height:32px;border:none;background:transparent;border-radius:9px;color:var(--muted);font-size:15px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;-webkit-tap-highlight-color:transparent}.pp-back:hover{background:var(--primary-soft);color:var(--primary)}.pp-header__title{margin:0;font-size:16px;font-weight:700;line-height:1.15}.pp-header__spacer{flex:1}.pp-icon-btn{width:32px;height:32px;border:none;background:transparent;border-radius:9px;color:var(--muted);font-size:15px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;-webkit-tap-highlight-color:transparent}.pp-icon-btn:hover{background:var(--primary-soft);color:var(--primary)}.pp-icon-btn--danger:hover{background:var(--danger-soft);color:var(--danger)}.pp-icon-btn--success{color:var(--success)}.pp-icon-btn--success:hover{background:var(--success-soft);color:var(--success)}.pp-icon-btn__spin{animation:pp-spin .8s linear infinite}@keyframes pp-spin{to{transform:rotate(360deg)}}.pp-banner{margin:10px 12px 0;padding:10px 12px;border-radius:12px;display:flex;align-items:center;gap:8px;font-size:13px}.pp-banner--error{background:var(--danger-soft);color:var(--danger)}.pp-banner--success{background:var(--success-soft);color:var(--success)}.pp-banner__close{margin-left:auto;border:none;background:transparent;color:inherit;cursor:pointer}.pp-overview{flex-shrink:0;display:grid;grid-template-columns:repeat(4,1fr);gap:4px;padding:10px 12px 0}.pp-overview__cell{background:var(--surface);border:1px solid var(--border);border-radius:0;padding:8px 4px;text-align:center}.pp-overview__value{display:block;font-size:16px;font-weight:700;line-height:1.1}.pp-overview__value--amber{color:var(--warning)}.pp-overview__value--danger{color:var(--danger)}.pp-overview__value--success{color:var(--success)}.pp-overview__label{display:block;margin-top:2px;font-size:8px;color:var(--muted-2);line-height:1.2}.pp-priority-bar{flex-shrink:0;padding:8px 12px 0}.pp-priority-bar__track{display:flex;height:5px;overflow:hidden;background:var(--border)}.pp-priority-bar__segment{height:100%}.pp-priority-bar__segment--high{background:var(--danger)}.pp-priority-bar__segment--medium{background:#eab308}.pp-priority-bar__segment--low{background:#22c55e}.pp-priority-bar__legend{display:flex;gap:10px;margin-top:4px;font-size:9px;color:var(--muted)}.pp-tabs{flex-shrink:0;display:flex;margin:10px 12px 0;border:1px solid var(--border);background:#f7f8fc}.pp-tabs__btn{flex:1;border:none;background:transparent;padding:8px 0;font-size:11px;font-weight:700;color:var(--muted);cursor:pointer;font-family:inherit}.pp-tabs__btn:first-child{border-right:1px solid var(--border)}.pp-tabs__btn--active{background:var(--surface);color:var(--text)}.pp-tabs__count{color:var(--muted-2);font-weight:700}.pp-filters{flex-shrink:0;display:flex;gap:4px;padding:8px 12px 0;overflow-x:auto}.pp-filter-chip{flex-shrink:0;border:1px solid var(--border);border-radius:0;background:var(--surface);color:var(--muted);font-size:10px;font-weight:600;padding:4px 8px;cursor:pointer;font-family:inherit}.pp-filter-chip--active{border-color:var(--primary);background:var(--primary-soft);color:var(--primary)}.pp-group-label{margin:4px 0 0;font-size:10px;font-weight:700;letter-spacing:.03em}.pp-group-label--high{color:var(--danger)}.pp-group-label--medium{color:#d97706}.pp-group-label--low{color:var(--success)}.pp-list{flex:1;overflow:auto;padding:8px 12px 12px;display:flex;flex-direction:column;gap:5px}.pp-priority{flex-shrink:0;width:18px;padding-top:2px;display:inline-flex;align-items:flex-start;justify-content:center;font-size:12px;font-weight:700}.pp-priority--high{color:#e11d48}.pp-priority--medium{color:#eab308}.pp-priority--low{color:#22c55e}.pp-priority--muted{opacity:.55}.pp-row{position:relative;display:flex;gap:6px;align-items:flex-start;padding:9px 10px;background:var(--surface);border:1px solid var(--border);border-radius:0}.pp-row--pending{border-left-width:3px}.pp-row--high{border-left-color:var(--danger)}.pp-row--medium{border-left-color:#eab308}.pp-row--low{border-left-color:#22c55e}.pp-row--purchased{opacity:.95;background:#fafbfc}.pp-row--clickable{cursor:pointer;transition:border-color .15s ease,background .15s ease}.pp-row--clickable:hover{border-color:color-mix(in srgb,var(--primary) 35%,var(--border));background:#f8fafc}.pp-row--clickable:focus-visible{outline:2px solid var(--primary);outline-offset:2px}.pp-row--menu-open{z-index:10;overflow:visible}.pp-row--clickable{cursor:pointer;transition:background .15s ease}.pp-row--clickable:hover{background:#f8fafc}.pp-row--clickable:focus-visible{outline:2px solid var(--primary);outline-offset:-2px}.pp-row__body{flex:1;min-width:0}.pp-row__top{display:flex;align-items:baseline;justify-content:space-between;gap:8px;margin-bottom:2px}.pp-row__main{flex:1;min-width:0}.pp-row__title{margin:0;font-size:14px;font-weight:700;line-height:1.25;min-width:0}.pp-row--compact .pp-row__title{font-size:14px}.pp-row__desc{margin:0 0 4px;font-size:12px;color:var(--muted);line-height:1.35;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.pp-row__qty{flex-shrink:0;font-size:12px;color:var(--primary);font-weight:700;white-space:nowrap}.pp-row__note{margin:0 0 4px;font-size:12px;color:var(--muted);line-height:1.35;display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden}.pp-row__meta{margin:4px 0 0;font-size:9px;color:var(--muted-2);min-width:0}.pp-row__comments{display:inline-flex;align-items:center;gap:3px;white-space:nowrap}.pp-row__comments i{font-size:9px}.pp-row__actions-col{display:flex;flex-direction:column;align-items:center;gap:6px;flex-shrink:0}.pp-row__purchase-note{margin:0 0 6px;font-size:13px;color:var(--success);line-height:1.4}.pp-row__purchase-note i{margin-right:4px;font-size:12px}.pp-row__confirm{flex-shrink:0;width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--success);border-radius:0;background:var(--success-soft);color:var(--success);font-size:12px;cursor:pointer;font-family:inherit;-webkit-tap-highlight-color:transparent}.pp-row__confirm:hover{background:var(--success);color:#fff}.pp-row__confirm:disabled{opacity:.6;cursor:not-allowed}.pp-row__menu-wrap{position:relative;flex-shrink:0;align-self:center}.pp-icon-btn--menu{color:var(--muted-2)}.pp-row-menu{position:absolute;top:calc(100% + 4px);right:0;min-width:148px;background:var(--surface);border:1px solid var(--border);border-radius:0;box-shadow:0 8px 24px #0f172a1f;padding:4px;z-index:20}.pp-row-menu__item{width:100%;border:none;background:transparent;border-radius:0;padding:8px 10px;display:inline-flex;align-items:center;gap:8px;font-size:12px;font-weight:600;color:var(--text);cursor:pointer;text-align:left;font-family:inherit}.pp-row-menu__item:not(:last-child){border-bottom:1px solid var(--border)}.pp-row-menu__item:hover{background:var(--primary-soft);color:var(--primary)}.pp-row-menu__item--danger{color:var(--danger)}.pp-row-menu__item--danger:hover{background:var(--danger-soft);color:var(--danger)}.pp-row-menu__badge{margin-left:auto;font-size:11px;font-weight:700;padding:2px 7px;border-radius:999px;background:var(--primary-soft);color:var(--primary)}.pp-empty{margin:12px 0;text-align:center;color:var(--muted-2);font-size:14px}.pp-empty--group{margin:8px 0 0;font-size:13px}.pp-add-wrap{flex-shrink:0;padding:12px;padding-bottom:calc(12px + env(safe-area-inset-bottom,0px));background:var(--surface);border-top:1px solid var(--border)}.pp-add{width:100%;border:none;border-radius:0;padding:11px 16px;background:var(--primary);color:#fff;font-size:15px;font-weight:700;display:inline-flex;align-items:center;justify-content:center;gap:8px;cursor:pointer}.pp-add:disabled{opacity:.6;cursor:not-allowed}.pp-overlay{position:fixed;inset:0;z-index:420;background:#0f172a80;display:flex;align-items:center;justify-content:center;padding:16px;padding-top:calc(16px + env(safe-area-inset-top,0px));padding-bottom:calc(16px + env(safe-area-inset-bottom,0px))}.pp-overlay--detail{background:#0f172a61;padding:12px}.pp-confirm{width:100%;max-width:360px;background:var(--surface);border-radius:0;padding:18px 16px}.pp-confirm__title{margin:0 0 8px;font-size:16px}.pp-confirm__body{margin:0 0 16px;color:var(--muted);font-size:14px}.pp-confirm__foot{display:flex;justify-content:flex-end;gap:8px}.pp-btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;border:none;border-radius:10px;padding:9px 16px;font-size:14px;font-weight:600;cursor:pointer}.pp-btn--ghost{background:transparent;color:var(--muted)}.pp-btn--danger{background:var(--danger);color:#fff}.pp-btn--primary{background:var(--primary);color:#fff}.pp-btn--success{background:var(--success-soft);border:1px solid var(--success);color:var(--success)}.pp-btn--block{width:100%}.pp-btn--danger-soft{background:color-mix(in srgb,var(--danger) 10%,var(--surface));border:1px solid color-mix(in srgb,var(--danger) 35%,var(--border));color:var(--danger)}.pp-icon-btn--soft{width:34px;height:34px;border-radius:10px;background:var(--surface-2, #f1f5f9);color:var(--muted)}.pp-detail{width:100%;max-width:420px;max-height:min(90vh,720px);background:var(--surface);border:1px solid var(--border);border-radius:16px;display:flex;flex-direction:column;overflow:hidden}.pp-detail__hero{padding:16px 16px 12px;border-bottom:1px solid color-mix(in srgb,var(--border) 70%,transparent)}.pp-detail__hero-top{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:10px}.pp-detail__priority{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:999px;font-size:11px;font-weight:700;background:var(--surface-2, #f1f5f9);color:var(--muted)}.pp-detail__priority--high{background:color-mix(in srgb,var(--danger) 12%,white);color:var(--danger)}.pp-detail__priority--medium{background:color-mix(in srgb,#d97706 12%,white);color:#d97706}.pp-detail__priority--low{background:color-mix(in srgb,var(--success) 12%,white);color:var(--success)}.pp-detail__priority .fa-solid{font-size:10px}.pp-detail__name{margin:0 0 8px;font-size:20px;font-weight:800;line-height:1.25;letter-spacing:-.02em;color:var(--text);overflow-wrap:anywhere}.pp-detail__meta{margin:0;display:flex;flex-wrap:wrap;gap:12px;font-size:12px;font-weight:600;color:var(--muted)}.pp-detail__meta span{display:inline-flex;align-items:center;gap:6px}.pp-detail__meta .fa-solid{font-size:11px;opacity:.75}.pp-detail__body{flex:1;overflow:auto;padding:14px 16px 16px;display:flex;flex-direction:column;gap:10px}.pp-detail__foot{display:flex;flex-direction:column;gap:8px;padding:12px 16px 16px;border-top:1px solid color-mix(in srgb,var(--border) 70%,transparent);background:color-mix(in srgb,var(--surface-2, #f8fafc) 50%,var(--surface))}.pp-detail__foot-row{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}.pp-detail__foot-row .pp-btn{width:100%;padding:9px 8px;font-size:13px}.pp-detail-card{padding:12px 14px;border-radius:14px;background:var(--surface-2, #f8fafc);border:1px solid color-mix(in srgb,var(--border) 80%,transparent)}.pp-detail-card__title{margin:0 0 8px;font-size:12px;font-weight:700;color:var(--muted)}.pp-detail-card__text{margin:0;font-size:13px;line-height:1.5;color:var(--text);overflow-wrap:anywhere}.pp-detail-kv{margin:0}.pp-detail-kv__row{display:flex;align-items:baseline;justify-content:space-between;gap:12px;padding:5px 0}.pp-detail-kv__row:not(:last-child){border-bottom:1px solid color-mix(in srgb,var(--border) 55%,transparent)}.pp-detail-kv__row dt{margin:0;font-size:12px;font-weight:500;color:var(--muted)}.pp-detail-kv__row dd{margin:0;font-size:13px;font-weight:700;color:var(--text);text-align:right;overflow-wrap:anywhere}.pp-detail-kv__value--overdue{color:var(--danger)}.pp-comments{margin-top:4px}.pp-comments__head{display:flex;align-items:center;gap:8px;margin-bottom:10px}.pp-comments__title{margin:0;font-size:13px;font-weight:700;color:var(--text)}.pp-comments__count{font-size:11px;font-weight:700;min-width:22px;height:22px;padding:0 7px;display:inline-flex;align-items:center;justify-content:center;border-radius:999px;background:var(--primary-soft);color:var(--primary)}.pp-comments__list{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:8px}.pp-comments__empty{font-size:12px;line-height:1.45;color:var(--muted-2);padding:10px 12px;border-radius:12px;background:var(--surface-2, #f8fafc);border:1px dashed color-mix(in srgb,var(--border) 90%,transparent)}.pp-comments__composer{display:flex;align-items:flex-end;gap:8px;margin-top:12px}.pp-comments__input{flex:1;min-width:0;resize:vertical;min-height:44px;padding:10px 12px;border:1px solid color-mix(in srgb,var(--border) 90%,transparent);border-radius:12px;background:var(--surface);color:var(--text);font-size:13px;font-family:inherit;outline:none;line-height:1.4}.pp-comments__input:focus{border-color:var(--primary)}.pp-comments__send{flex-shrink:0;width:40px;height:40px;display:inline-flex;align-items:center;justify-content:center;border:none;border-radius:12px;background:var(--primary);color:#fff;font-size:14px;cursor:pointer;font-family:inherit;-webkit-tap-highlight-color:transparent}.pp-comments__send:hover:not(:disabled){filter:brightness(1.05)}.pp-comments__send:disabled{opacity:.5;cursor:not-allowed}.pp-comment{border-radius:12px;background:var(--surface-2, #f8fafc);border:1px solid color-mix(in srgb,var(--border) 80%,transparent);padding:10px 12px}.pp-comment__head{display:flex;align-items:baseline;justify-content:space-between;gap:8px;margin-bottom:6px}.pp-comment__author{font-size:12px;font-weight:700;color:var(--text)}.pp-comment__date{font-size:10px;font-weight:600;color:var(--muted-2);font-variant-numeric:tabular-nums}.pp-comment__text{margin:0;font-size:13px;color:var(--muted);line-height:1.45;overflow-wrap:anywhere;white-space:pre-wrap}.pp-comment__link{color:var(--primary);text-decoration:underline;text-underline-offset:2px;word-break:break-all}.pp-comment__link:hover{filter:brightness(.92)}.pp-purchase-entry{border:1px solid var(--border);background:#fafbfc;padding:12px;margin-bottom:10px}.pp-purchase-entry:last-child{margin-bottom:0}.pp-purchase-entry__head{display:flex;flex-wrap:wrap;align-items:baseline;gap:6px 10px;margin-bottom:8px}.pp-purchase-entry__index{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.03em;color:var(--primary)}.pp-purchase-entry__date{font-size:13px;font-weight:700;color:var(--text)}.pp-purchase-entry__qty{margin-left:auto;font-size:12px;font-weight:700;color:var(--muted)}.pp-purchase-entry__note{margin:0 0 10px;font-size:12px;line-height:1.4;color:var(--muted);overflow-wrap:anywhere}.pp-comments--nested{margin-top:0;padding-top:0;border-top:none}.pp-history-sheet{width:min(100%,520px);max-height:min(92vh,720px);margin:0 auto;background:var(--surface);border-radius:0;display:flex;flex-direction:column;overflow:hidden}.pp-history-sheet__head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding:16px;border-bottom:1px solid var(--border)}.pp-history-sheet__title{margin:0;font-size:17px;font-weight:700}.pp-history-sheet__sub{margin:4px 0 0;font-size:13px;color:var(--muted)}.pp-history-sheet__body{flex:1;overflow:auto;padding:12px 16px}.pp-history-sheet__foot{display:flex;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid var(--border)}.pp-history-table{width:100%;border-collapse:collapse;font-size:13px}.pp-history-table th,.pp-history-table td{padding:10px 8px;text-align:left;border-bottom:1px solid var(--border);vertical-align:top}.pp-history-table th{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.03em;color:var(--muted-2);background:#f8fafc;position:sticky;top:0}.pp-history-table tbody tr:last-child td{border-bottom:none}.pp-history-table td:first-child{width:36px;color:var(--muted-2);font-weight:700}.pp-history-table td:nth-child(3){white-space:nowrap}\n"] }]
|
|
23750
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section class=\"pp-root\" role=\"dialog\" aria-modal=\"true\" aria-label=\"C\u1EA7n mua s\u1EAFm\">\n <header class=\"pp-header\">\n <button type=\"button\" class=\"pp-back\" (click)=\"back.emit()\" aria-label=\"Quay l\u1EA1i\">\n <i class=\"pi pi-chevron-left\" aria-hidden=\"true\"></i>\n </button>\n <h1 class=\"pp-header__title\">C\u1EA7n mua s\u1EAFm</h1>\n <span class=\"pp-header__spacer\"></span>\n <button\n type=\"button\"\n class=\"pp-icon-btn\"\n (click)=\"load(true)\"\n [disabled]=\"loading()\"\n title=\"L\u00E0m m\u1EDBi danh s\u00E1ch\"\n aria-label=\"L\u00E0m m\u1EDBi danh s\u00E1ch\"\n >\n <i class=\"pi pi-refresh\" [class.pp-icon-btn__spin]=\"loading()\" aria-hidden=\"true\"></i>\n </button>\n </header>\n\n @if (errorMsg()) {\n <div class=\"pp-banner pp-banner--error\" role=\"alert\">\n <span>{{ errorMsg() }}</span>\n <button type=\"button\" class=\"pp-banner__close\" (click)=\"clearMessages()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\n </button>\n </div>\n }\n\n @if (successMsg()) {\n <div class=\"pp-banner pp-banner--success\" role=\"status\">\n <span>{{ successMsg() }}</span>\n <button type=\"button\" class=\"pp-banner__close\" (click)=\"clearMessages()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\n </button>\n </div>\n }\n\n <div class=\"pp-overview\" aria-label=\"T\u1ED5ng quan\">\n <div class=\"pp-overview__cell\">\n <span class=\"pp-overview__value pp-overview__value--amber\">{{ pendingItems().length }}</span>\n <span class=\"pp-overview__label\">Ch\u01B0a mua</span>\n </div>\n <div class=\"pp-overview__cell\">\n <span class=\"pp-overview__value pp-overview__value--danger\">{{ highPriorityCount() }}</span>\n <span class=\"pp-overview__label\">\u01AFu ti\u00EAn cao</span>\n </div>\n <div class=\"pp-overview__cell\">\n <span class=\"pp-overview__value pp-overview__value--danger\">{{ overdueCount() }}</span>\n <span class=\"pp-overview__label\">Qu\u00E1 h\u1EA1n</span>\n </div>\n <div class=\"pp-overview__cell\">\n <span class=\"pp-overview__value pp-overview__value--success\">{{ purchasedThisMonthCount() }}</span>\n <span class=\"pp-overview__label\">\u0110\u00E3 mua th\u00E1ng</span>\n </div>\n </div>\n\n @if (pendingItems().length > 0) {\n <div class=\"pp-priority-bar\" aria-hidden=\"true\">\n <div class=\"pp-priority-bar__track\">\n @for (segment of prioritySegments(); track segment.priority) {\n <span\n class=\"pp-priority-bar__segment\"\n [class.pp-priority-bar__segment--high]=\"segment.priority === 'high'\"\n [class.pp-priority-bar__segment--medium]=\"segment.priority === 'medium'\"\n [class.pp-priority-bar__segment--low]=\"segment.priority === 'low'\"\n [style.width.%]=\"segment.percent\"\n ></span>\n }\n </div>\n <div class=\"pp-priority-bar__legend\">\n <span>\u2191 Cao {{ priorityCounts().high }}</span>\n <span>\u2014 TB {{ priorityCounts().medium }}</span>\n <span>\u2193 Th\u1EA5p {{ priorityCounts().low }}</span>\n </div>\n </div>\n }\n\n <div class=\"pp-tabs\" role=\"tablist\" aria-label=\"Tr\u1EA1ng th\u00E1i mua\">\n <button\n type=\"button\"\n class=\"pp-tabs__btn\"\n role=\"tab\"\n [class.pp-tabs__btn--active]=\"activeTab() === 'pending'\"\n [attr.aria-selected]=\"activeTab() === 'pending'\"\n (click)=\"setTab('pending')\"\n >\n C\u1EA7n mua <span class=\"pp-tabs__count\">{{ pendingItems().length }}</span>\n </button>\n <button\n type=\"button\"\n class=\"pp-tabs__btn\"\n role=\"tab\"\n [class.pp-tabs__btn--active]=\"activeTab() === 'purchased'\"\n [attr.aria-selected]=\"activeTab() === 'purchased'\"\n (click)=\"setTab('purchased')\"\n >\n \u0110\u00E3 mua <span class=\"pp-tabs__count\">{{ purchasedItems().length }}</span>\n </button>\n </div>\n\n @if (activeTab() === 'pending') {\n <div class=\"pp-filters\" role=\"group\" aria-label=\"L\u1ECDc theo \u01B0u ti\u00EAn\">\n @for (chip of priorityFilterOptions; track chip.id) {\n <button\n type=\"button\"\n class=\"pp-filter-chip\"\n [class.pp-filter-chip--active]=\"priorityFilter() === chip.id\"\n (click)=\"setPriorityFilter(chip.id)\"\n >\n {{ chip.label }}\n </button>\n }\n </div>\n }\n\n <div class=\"pp-list\">\n @if (activeTab() === 'pending') {\n @if (priorityFilter() === 'all') {\n @for (group of pendingPriorityGroups(); track group.priority) {\n @if (group.items.length > 0) {\n <p\n class=\"pp-group-label\"\n [class.pp-group-label--high]=\"group.priority === 'high'\"\n [class.pp-group-label--medium]=\"group.priority === 'medium'\"\n [class.pp-group-label--low]=\"group.priority === 'low'\"\n >\n {{ priorityGroupHeading(group.priority) }}\n </p>\n @for (item of group.items; track itemKey(item)) {\n <ng-container *ngTemplateOutlet=\"pendingRow; context: { $implicit: item }\" />\n }\n }\n }\n } @else {\n <p\n class=\"pp-group-label\"\n [class.pp-group-label--high]=\"priorityFilter() === 'high'\"\n [class.pp-group-label--medium]=\"priorityFilter() === 'medium'\"\n [class.pp-group-label--low]=\"priorityFilter() === 'low'\"\n >\n {{ filteredPriorityHeading() }}\n </p>\n @for (item of filteredPendingItems(); track itemKey(item)) {\n <ng-container *ngTemplateOutlet=\"pendingRow; context: { $implicit: item }\" />\n }\n }\n\n @if (filteredPendingItems().length === 0 && !loading()) {\n <p class=\"pp-empty\">Kh\u00F4ng c\u00F2n m\u00F3n n\u00E0o c\u1EA7n mua.</p>\n }\n } @else {\n @for (item of purchasedItems(); track itemKey(item)) {\n <ng-container *ngTemplateOutlet=\"purchasedRow; context: { $implicit: item }\" />\n } @empty {\n @if (!loading()) {\n <p class=\"pp-empty\">Ch\u01B0a c\u00F3 m\u00F3n n\u00E0o \u0111\u00E3 mua.</p>\n }\n }\n }\n\n @if (loading() && pendingItems().length === 0 && purchasedItems().length === 0) {\n <p class=\"pp-empty\">\u0110ang t\u1EA3i\u2026</p>\n }\n </div>\n\n <div class=\"pp-add-wrap\">\n <button type=\"button\" class=\"pp-add\" (click)=\"openAdd()\" [disabled]=\"saving()\">\n <i class=\"pi pi-plus\" aria-hidden=\"true\"></i>\n <span>Th\u00EAm m\u00F3n</span>\n </button>\n </div>\n</section>\n\n<ng-template #pendingRow let-item>\n <article\n class=\"pp-row pp-row--pending pp-row--clickable\"\n [class.pp-row--menu-open]=\"rowMenuKey() === itemKey(item)\"\n [class.pp-row--high]=\"item.priority === 'high'\"\n [class.pp-row--medium]=\"item.priority === 'medium'\"\n [class.pp-row--low]=\"item.priority === 'low'\"\n (click)=\"openPendingDetailItem(item)\"\n role=\"button\"\n tabindex=\"0\"\n (keydown.enter)=\"openPendingDetailItem(item)\"\n (keydown.space)=\"$event.preventDefault(); openPendingDetailItem(item)\"\n [attr.aria-label]=\"'Xem chi ti\u1EBFt ' + item.itemName\"\n >\n <span\n class=\"pp-priority\"\n [class.pp-priority--high]=\"item.priority === 'high'\"\n [class.pp-priority--medium]=\"item.priority === 'medium'\"\n [class.pp-priority--low]=\"item.priority === 'low'\"\n [attr.title]=\"'\u01AFu ti\u00EAn ' + priorityLabel(item.priority)\"\n >\n <i class=\"fa-solid\" [ngClass]=\"priorityIcon(item.priority)\" aria-hidden=\"true\"></i>\n </span>\n\n <div class=\"pp-row__body\">\n <div class=\"pp-row__top\">\n <h2 class=\"pp-row__title\">{{ item.itemName }}</h2>\n @if (item.quantity) {\n <span class=\"pp-row__qty\">{{ item.quantity }}</span>\n }\n </div>\n @if (rowSubtitle(item); as subtitle) {\n <p class=\"pp-row__desc\">{{ subtitle }}</p>\n }\n <p class=\"pp-row__meta\">\n Th\u00EAm {{ formatDate(item.createdDate) }}\n @if (item.createdBy) {\n \u00B7 {{ item.createdBy }}\n }\n <span class=\"pp-row__comments\" [attr.title]=\"commentCountForRequest(item.requestId) + ' b\u00ECnh lu\u1EADn'\">\n \u00B7 <i class=\"pi pi-comment\" aria-hidden=\"true\"></i>\n {{ commentCountForRequest(item.requestId) }}\n </span>\n </p>\n </div>\n\n <div class=\"pp-row__actions-col\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"pp-row__confirm\"\n (click)=\"openPurchaseConfirm(item)\"\n [disabled]=\"saving()\"\n title=\"\u0110\u00E1nh d\u1EA5u \u0111\u00E3 mua\"\n aria-label=\"\u0110\u00E1nh d\u1EA5u \u0111\u00E3 mua\"\n >\n <i class=\"pi pi-check\" aria-hidden=\"true\"></i>\n </button>\n <div class=\"pp-row__menu-wrap\">\n <button\n type=\"button\"\n class=\"pp-icon-btn pp-icon-btn--menu\"\n (click)=\"toggleRowMenu(item, $event)\"\n title=\"Tu\u1EF3 ch\u1ECDn\"\n aria-label=\"Tu\u1EF3 ch\u1ECDn\"\n aria-haspopup=\"menu\"\n [attr.aria-expanded]=\"rowMenuKey() === itemKey(item)\"\n >\n <i class=\"pi pi-ellipsis-v\" aria-hidden=\"true\"></i>\n </button>\n @if (rowMenuKey() === itemKey(item)) {\n <div class=\"pp-row-menu\" role=\"menu\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"pp-row-menu__item\"\n role=\"menuitem\"\n (click)=\"openPendingDetail(item, $event)\"\n >\n Xem chi ti\u1EBFt\n </button>\n <button\n type=\"button\"\n class=\"pp-row-menu__item\"\n role=\"menuitem\"\n (click)=\"openEdit(item); rowMenuKey.set(null)\"\n >\n S\u1EEDa\n </button>\n <button\n type=\"button\"\n class=\"pp-row-menu__item pp-row-menu__item--danger\"\n role=\"menuitem\"\n (click)=\"askDelete(item, $event)\"\n >\n Xo\u00E1\n </button>\n </div>\n }\n </div>\n </div>\n </article>\n</ng-template>\n\n<ng-template #purchasedRow let-item>\n <article\n class=\"pp-row pp-row--purchased pp-row--clickable\"\n [class.pp-row--menu-open]=\"rowMenuKey() === itemKey(item)\"\n (click)=\"openPurchaseDetail(item, $event)\"\n role=\"button\"\n tabindex=\"0\"\n (keydown.enter)=\"openPurchaseDetail(item, $event)\"\n (keydown.space)=\"$event.preventDefault(); openPurchaseDetailItem(item)\"\n [attr.aria-label]=\"'Xem l\u1ECBch s\u1EED mua ' + item.itemName\"\n >\n <span\n class=\"pp-priority pp-priority--muted\"\n [class.pp-priority--high]=\"item.priority === 'high'\"\n [class.pp-priority--medium]=\"item.priority === 'medium'\"\n [class.pp-priority--low]=\"item.priority === 'low'\"\n [attr.title]=\"'\u01AFu ti\u00EAn ' + priorityLabel(item.priority)\"\n >\n <i class=\"fa-solid\" [ngClass]=\"priorityIcon(item.priority)\" aria-hidden=\"true\"></i>\n </span>\n\n <div class=\"pp-row__body\">\n <div class=\"pp-row__top\">\n <h2 class=\"pp-row__title\">{{ item.itemName }}</h2>\n @if (item.quantity) {\n <span class=\"pp-row__qty\">{{ item.quantity }}</span>\n }\n </div>\n <p class=\"pp-row__desc\">\n @if (item.description) {\n {{ item.description }}\n } @else if (item.purchasedDate) {\n Mua {{ formatDate(item.purchasedDate) }}\n }\n </p>\n <p class=\"pp-row__meta\">\n @if (item.purchasedDate) {\n \u0110\u00E3 mua {{ formatDate(item.purchasedDate) }}\n }\n \u00B7 {{ purchaseCountForProduct(item.productId) }} l\u1EA7n\n <span class=\"pp-row__comments\" [attr.title]=\"commentCountForRequest(item.requestId) + ' b\u00ECnh lu\u1EADn'\">\n \u00B7 <i class=\"pi pi-comment\" aria-hidden=\"true\"></i>\n {{ commentCountForRequest(item.requestId) }}\n </span>\n </p>\n </div>\n\n <div class=\"pp-row__menu-wrap\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"pp-icon-btn pp-icon-btn--menu\"\n (click)=\"toggleRowMenu(item, $event)\"\n title=\"Tu\u1EF3 ch\u1ECDn\"\n aria-label=\"Tu\u1EF3 ch\u1ECDn\"\n aria-haspopup=\"menu\"\n [attr.aria-expanded]=\"rowMenuKey() === itemKey(item)\"\n >\n <i class=\"pi pi-ellipsis-v\" aria-hidden=\"true\"></i>\n </button>\n @if (rowMenuKey() === itemKey(item)) {\n <div class=\"pp-row-menu\" role=\"menu\" (click)=\"$event.stopPropagation()\">\n <button\n type=\"button\"\n class=\"pp-row-menu__item\"\n role=\"menuitem\"\n (click)=\"openPurchaseDetail(item, $event)\"\n >\n Xem chi ti\u1EBFt\n <span class=\"pp-row-menu__badge\">{{ purchaseCountForProduct(item.productId) }}</span>\n </button>\n <button\n type=\"button\"\n class=\"pp-row-menu__item\"\n role=\"menuitem\"\n (click)=\"duplicateFromPurchased(item, $event)\"\n >\n Mua l\u1EA1i\n </button>\n </div>\n }\n </div>\n </article>\n</ng-template>\n\n@if (isDialogOpen()) {\n <app-preparing-purchase-dialog\n [heading]=\"dialogHeading()\"\n [mode]=\"dialogMode()\"\n [products]=\"products()\"\n [initialProductId]=\"dialogProductId()\"\n [initialProductName]=\"dialogProductName()\"\n [initialProductDescription]=\"dialogProductDescription()\"\n [initialAmount]=\"dialogAmount()\"\n [initialNote]=\"dialogNote()\"\n [initialAssignee]=\"dialogAssignee()\"\n [initialDueDate]=\"dialogDueDate()\"\n [initialPriority]=\"dialogPriority()\"\n [saving]=\"saving()\"\n (cancel)=\"closeDialog()\"\n (save)=\"onSave($event)\"\n />\n}\n\n@if (purchaseConfirm()) {\n <app-preparing-purchase-confirm-dialog\n [itemName]=\"purchaseConfirm()?.itemName ?? ''\"\n [quantity]=\"purchaseConfirm()?.quantity ?? ''\"\n [saving]=\"saving()\"\n (cancel)=\"cancelPurchaseConfirm()\"\n (confirm)=\"onConfirmPurchase($event)\"\n />\n}\n\n@if (pendingDetailItem(); as detail) {\n <div class=\"pp-overlay pp-overlay--detail\" (click)=\"closePendingDetail()\" role=\"presentation\">\n <div\n class=\"pp-detail\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"detail.itemName\"\n (click)=\"$event.stopPropagation()\"\n >\n <header class=\"pp-detail__hero\">\n <div class=\"pp-detail__hero-top\">\n <span\n class=\"pp-detail__priority\"\n [class.pp-detail__priority--high]=\"detail.priority === 'high'\"\n [class.pp-detail__priority--medium]=\"detail.priority === 'medium'\"\n [class.pp-detail__priority--low]=\"detail.priority === 'low'\"\n >\n <i class=\"fa-solid\" [ngClass]=\"priorityIcon(detail.priority)\" aria-hidden=\"true\"></i>\n {{ priorityLabel(detail.priority) }}\n </span>\n <button type=\"button\" class=\"pp-icon-btn pp-icon-btn--soft\" (click)=\"closePendingDetail()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"fa-solid fa-xmark\" aria-hidden=\"true\"></i>\n </button>\n </div>\n <h2 class=\"pp-detail__name\">{{ detail.itemName }}</h2>\n <p class=\"pp-detail__meta\">\n <span>\n <i class=\"fa-solid fa-user\" aria-hidden=\"true\"></i>\n {{ detail.createdBy || 'guest' }}\n </span>\n <span>\n <i class=\"fa-solid fa-clock\" aria-hidden=\"true\"></i>\n {{ formatDateTime(detail.createdDate) }}\n </span>\n </p>\n </header>\n\n <div class=\"pp-detail__body\">\n <section class=\"pp-detail-card\" aria-label=\"Y\u00EAu c\u1EA7u mua\">\n <h3 class=\"pp-detail-card__title\">Y\u00EAu c\u1EA7u mua</h3>\n <dl class=\"pp-detail-kv\">\n <div class=\"pp-detail-kv__row\">\n <dt>S\u1ED1 l\u01B0\u1EE3ng</dt>\n <dd>{{ detail.quantity || '\u2014' }}</dd>\n </div>\n <div class=\"pp-detail-kv__row\">\n <dt>Ng\u01B0\u1EDDi ph\u1EE5 tr\u00E1ch</dt>\n <dd>{{ itemAssignee(detail) || '\u2014' }}</dd>\n </div>\n <div class=\"pp-detail-kv__row\">\n <dt>Th\u1EDDi h\u1EA1n mua</dt>\n <dd [class.pp-detail-kv__value--overdue]=\"isItemOverdue(detail)\">\n {{ itemDueDate(detail) || '\u2014' }}\n </dd>\n </div>\n </dl>\n </section>\n\n @if (detail.description?.trim()) {\n <section class=\"pp-detail-card\" aria-label=\"M\u00F4 t\u1EA3 s\u1EA3n ph\u1EA9m\">\n <h3 class=\"pp-detail-card__title\">M\u00F4 t\u1EA3</h3>\n <p class=\"pp-detail-card__text\">{{ detail.description }}</p>\n </section>\n }\n\n @if (itemNoteText(detail); as noteText) {\n <section class=\"pp-detail-card\" aria-label=\"Ghi ch\u00FA\">\n <h3 class=\"pp-detail-card__title\">Ghi ch\u00FA</h3>\n <p class=\"pp-detail-card__text\">{{ noteText }}</p>\n </section>\n }\n\n <section class=\"pp-comments\" aria-label=\"B\u00ECnh lu\u1EADn\">\n <div class=\"pp-comments__head\">\n <h3 class=\"pp-comments__title\">B\u00ECnh lu\u1EADn</h3>\n <span class=\"pp-comments__count\">{{ detailComments().length }}</span>\n </div>\n\n <ul class=\"pp-comments__list\">\n @for (comment of detailComments(); track comment.id) {\n <li class=\"pp-comment\">\n <div class=\"pp-comment__head\">\n <span class=\"pp-comment__author\">{{ comment.createdBy || 'guest' }}</span>\n <span class=\"pp-comment__date\">{{ formatDateTime(comment.createdDate) }}</span>\n </div>\n <p class=\"pp-comment__text\">\n @for (part of commentSegments(comment.text); track $index) {\n @if (part.type === 'link') {\n <a\n class=\"pp-comment__link\"\n [href]=\"part.href\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n (click)=\"$event.stopPropagation()\"\n >{{ part.value }}</a>\n } @else {\n {{ part.value }}\n }\n }\n </p>\n </li>\n } @empty {\n <li class=\"pp-comments__empty\">Ch\u01B0a c\u00F3 b\u00ECnh lu\u1EADn \u2014 th\u00EAm ghi ch\u00FA cho m\u1ECDi ng\u01B0\u1EDDi c\u00F9ng xem.</li>\n }\n </ul>\n\n <div class=\"pp-comments__composer\">\n <textarea\n class=\"pp-comments__input\"\n rows=\"2\"\n [value]=\"commentDraft()\"\n (input)=\"onCommentDraft($any($event.target).value)\"\n [disabled]=\"commentSaving()\"\n placeholder=\"Th\u00EAm b\u00ECnh lu\u1EADn\u2026\"\n aria-label=\"N\u1ED9i dung b\u00ECnh lu\u1EADn\"\n ></textarea>\n <button\n type=\"button\"\n class=\"pp-comments__send\"\n (click)=\"submitComment()\"\n [disabled]=\"commentSaving() || !commentDraft().trim()\"\n title=\"G\u1EEDi b\u00ECnh lu\u1EADn\"\n aria-label=\"G\u1EEDi b\u00ECnh lu\u1EADn\"\n >\n @if (commentSaving()) {\n <i class=\"fa-solid fa-spinner fa-spin\" aria-hidden=\"true\"></i>\n } @else {\n <i class=\"fa-solid fa-paper-plane\" aria-hidden=\"true\"></i>\n }\n </button>\n </div>\n </section>\n </div>\n\n <footer class=\"pp-detail__foot\">\n <button\n type=\"button\"\n class=\"pp-btn pp-btn--success pp-btn--block\"\n (click)=\"openPurchaseConfirm(detail); closePendingDetail()\"\n [disabled]=\"saving()\"\n >\n <i class=\"fa-solid fa-check\" aria-hidden=\"true\"></i>\n \u0110\u00E3 mua\n </button>\n <div class=\"pp-detail__foot-row\">\n <button\n type=\"button\"\n class=\"pp-btn pp-btn--ghost\"\n (click)=\"openEdit(detail); closePendingDetail()\"\n [disabled]=\"saving()\"\n >\n <i class=\"fa-solid fa-pencil\" aria-hidden=\"true\"></i>\n S\u1EEDa\n </button>\n <button\n type=\"button\"\n class=\"pp-btn pp-btn--danger-soft\"\n (click)=\"askDeleteFromDetail(detail)\"\n [disabled]=\"saving()\"\n >\n <i class=\"fa-solid fa-trash\" aria-hidden=\"true\"></i>\n X\u00F3a\n </button>\n <button type=\"button\" class=\"pp-btn pp-btn--ghost\" (click)=\"closePendingDetail()\">\n \u0110\u00F3ng\n </button>\n </div>\n </footer>\n </div>\n </div>\n}\n\n@if (purchaseDetailProductId() && purchaseDetailItem(); as detailItem) {\n <div class=\"pp-overlay\" (click)=\"closePurchaseDetail()\" role=\"presentation\">\n <div\n class=\"pp-history-sheet\"\n role=\"dialog\"\n aria-modal=\"true\"\n [attr.aria-label]=\"'L\u1ECBch s\u1EED mua ' + detailItem.itemName\"\n (click)=\"$event.stopPropagation()\"\n >\n <header class=\"pp-history-sheet__head\">\n <div>\n <h2 class=\"pp-history-sheet__title\">{{ detailItem.itemName }}</h2>\n <p class=\"pp-history-sheet__sub\">\n \u0110\u00E3 mua {{ purchaseHistoryRows().length }} l\u1EA7n\n </p>\n </div>\n <button type=\"button\" class=\"pp-icon-btn\" (click)=\"closePurchaseDetail()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\" aria-hidden=\"true\"></i>\n </button>\n </header>\n\n <div class=\"pp-history-sheet__body\">\n @for (row of purchaseHistoryRows(); track itemKey(row); let idx = $index) {\n <article class=\"pp-purchase-entry\">\n <header class=\"pp-purchase-entry__head\">\n <span class=\"pp-purchase-entry__index\">L\u1EA7n {{ purchaseHistoryRows().length - idx }}</span>\n <span class=\"pp-purchase-entry__date\">\n {{ formatDate(row.purchasedDate ?? row.createdDate) }}\n </span>\n @if (row.quantity) {\n <span class=\"pp-purchase-entry__qty\">{{ row.quantity }}</span>\n }\n </header>\n\n @if (displayPurchaseNote(row) !== '\u2014') {\n <p class=\"pp-purchase-entry__note\">{{ displayPurchaseNote(row) }}</p>\n }\n\n <section class=\"pp-comments pp-comments--nested\" aria-label=\"B\u00ECnh lu\u1EADn l\u1EA7n mua\">\n <h3 class=\"pp-comments__title\">\n B\u00ECnh lu\u1EADn\n <span class=\"pp-comments__count\">{{ commentsForRequest(row.requestId).length }}</span>\n </h3>\n\n <ul class=\"pp-comments__list\">\n @for (comment of commentsForRequest(row.requestId); track comment.id) {\n <li class=\"pp-comment\">\n <div class=\"pp-comment__head\">\n <span class=\"pp-comment__author\">{{ comment.createdBy || 'guest' }}</span>\n <span class=\"pp-comment__date\">{{ formatDate(comment.createdDate) }}</span>\n </div>\n <p class=\"pp-comment__text\">{{ comment.text }}</p>\n </li>\n } @empty {\n <li class=\"pp-comments__empty\">Ch\u01B0a c\u00F3 b\u00ECnh lu\u1EADn cho l\u1EA7n mua n\u00E0y.</li>\n }\n </ul>\n\n <div class=\"pp-comments__composer\">\n <textarea\n class=\"pp-comments__input\"\n rows=\"2\"\n [value]=\"historyCommentDraft(row.requestId)\"\n (input)=\"onHistoryCommentDraft(row.requestId, $any($event.target).value)\"\n [disabled]=\"isHistoryCommentSaving(row.requestId)\"\n placeholder=\"Th\u00EAm b\u00ECnh lu\u1EADn cho l\u1EA7n mua n\u00E0y\u2026\"\n [attr.aria-label]=\"'B\u00ECnh lu\u1EADn l\u1EA7n ' + (purchaseHistoryRows().length - idx)\"\n ></textarea>\n <button\n type=\"button\"\n class=\"pp-comments__send\"\n (click)=\"submitHistoryComment(row)\"\n [disabled]=\"isHistoryCommentSaving(row.requestId) || !historyCommentDraft(row.requestId).trim()\"\n title=\"G\u1EEDi b\u00ECnh lu\u1EADn\"\n aria-label=\"G\u1EEDi b\u00ECnh lu\u1EADn\"\n >\n @if (isHistoryCommentSaving(row.requestId)) {\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i>\n } @else {\n <i class=\"pi pi-send\" aria-hidden=\"true\"></i>\n }\n </button>\n </div>\n </section>\n </article>\n }\n </div>\n\n <footer class=\"pp-history-sheet__foot\">\n <button type=\"button\" class=\"pp-btn pp-btn--ghost\" (click)=\"closePurchaseDetail()\">\u0110\u00F3ng</button>\n <button\n type=\"button\"\n class=\"pp-btn pp-btn--primary\"\n (click)=\"duplicateFromPurchased(detailItem, $event); closePurchaseDetail()\"\n >\n Mua l\u1EA1i\n </button>\n </footer>\n </div>\n </div>\n}\n\n@if (deleteConfirm()) {\n <div class=\"pp-overlay\" (click)=\"cancelDelete()\" role=\"presentation\">\n <div\n class=\"pp-confirm\"\n role=\"alertdialog\"\n aria-modal=\"true\"\n aria-label=\"Xo\u00E1 m\u00F3n\"\n (click)=\"$event.stopPropagation()\"\n >\n <h2 class=\"pp-confirm__title\">Xo\u00E1 m\u00F3n n\u00E0y?</h2>\n <p class=\"pp-confirm__body\">{{ deleteConfirm()?.itemName }}</p>\n <div class=\"pp-confirm__foot\">\n <button type=\"button\" class=\"pp-btn pp-btn--ghost\" (click)=\"cancelDelete()\" [disabled]=\"saving()\">\n Hu\u1EF7\n </button>\n <button type=\"button\" class=\"pp-btn pp-btn--danger\" (click)=\"confirmDelete()\" [disabled]=\"saving()\">\n @if (saving()) {\n <i class=\"pi pi-spin pi-spinner\" aria-hidden=\"true\"></i>\n \u0110ang xo\u00E1\u2026\n } @else {\n Xo\u00E1\n }\n </button>\n </div>\n </div>\n </div>\n}\n", styles: [":host{--bg: #f6f7fb;--surface: #ffffff;--border: #e6e8ef;--primary: #5b6cff;--primary-soft: #eef0ff;--text: #0f172a;--muted: #475569;--muted-2: #94a3b8;--success-soft: #ecfdf5;--success: #059669;--warning-soft: #fff7ed;--warning: #ea580c;--danger: #e11d48;--danger-soft: #fff1f2;position:fixed;inset:0;z-index:200;background:var(--bg);display:flex;flex-direction:column;color:var(--text);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;animation:pp-slide .22s cubic-bezier(.22,1,.36,1)}@keyframes pp-slide{0%{transform:translate(8px);opacity:0}to{transform:translate(0);opacity:1}}.pp-root{display:flex;flex-direction:column;flex:1;min-height:0}.pp-header{flex-shrink:0;display:flex;align-items:center;gap:6px;padding:8px 10px;padding-top:calc(8px + env(safe-area-inset-top,0px));background:var(--surface);border-bottom:1px solid var(--border)}.pp-back{width:32px;height:32px;border:none;background:transparent;border-radius:9px;color:var(--muted);font-size:15px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;-webkit-tap-highlight-color:transparent}.pp-back:hover{background:var(--primary-soft);color:var(--primary)}.pp-header__title{margin:0;font-size:16px;font-weight:700;line-height:1.15}.pp-header__spacer{flex:1}.pp-icon-btn{width:32px;height:32px;border:none;background:transparent;border-radius:9px;color:var(--muted);font-size:15px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;-webkit-tap-highlight-color:transparent}.pp-icon-btn:hover{background:var(--primary-soft);color:var(--primary)}.pp-icon-btn--danger:hover{background:var(--danger-soft);color:var(--danger)}.pp-icon-btn--success{color:var(--success)}.pp-icon-btn--success:hover{background:var(--success-soft);color:var(--success)}.pp-icon-btn__spin{animation:pp-spin .8s linear infinite}@keyframes pp-spin{to{transform:rotate(360deg)}}.pp-banner{margin:10px 12px 0;padding:10px 12px;border-radius:12px;display:flex;align-items:center;gap:8px;font-size:13px}.pp-banner--error{background:var(--danger-soft);color:var(--danger)}.pp-banner--success{background:var(--success-soft);color:var(--success)}.pp-banner__close{margin-left:auto;border:none;background:transparent;color:inherit;cursor:pointer}.pp-overview{flex-shrink:0;display:grid;grid-template-columns:repeat(4,1fr);gap:4px;padding:10px 12px 0}.pp-overview__cell{background:var(--surface);border:1px solid var(--border);border-radius:0;padding:8px 4px;text-align:center}.pp-overview__value{display:block;font-size:16px;font-weight:700;line-height:1.1}.pp-overview__value--amber{color:var(--warning)}.pp-overview__value--danger{color:var(--danger)}.pp-overview__value--success{color:var(--success)}.pp-overview__label{display:block;margin-top:2px;font-size:8px;color:var(--muted-2);line-height:1.2}.pp-priority-bar{flex-shrink:0;padding:8px 12px 0}.pp-priority-bar__track{display:flex;height:5px;overflow:hidden;background:var(--border)}.pp-priority-bar__segment{height:100%}.pp-priority-bar__segment--high{background:var(--danger)}.pp-priority-bar__segment--medium{background:#eab308}.pp-priority-bar__segment--low{background:#22c55e}.pp-priority-bar__legend{display:flex;gap:10px;margin-top:4px;font-size:9px;color:var(--muted)}.pp-tabs{flex-shrink:0;display:flex;margin:10px 12px 0;border:1px solid var(--border);background:#f7f8fc}.pp-tabs__btn{flex:1;border:none;background:transparent;padding:8px 0;font-size:11px;font-weight:700;color:var(--muted);cursor:pointer;font-family:inherit}.pp-tabs__btn:first-child{border-right:1px solid var(--border)}.pp-tabs__btn--active{background:var(--surface);color:var(--text)}.pp-tabs__count{color:var(--muted-2);font-weight:700}.pp-filters{flex-shrink:0;display:flex;gap:4px;padding:8px 12px 0;overflow-x:auto}.pp-filter-chip{flex-shrink:0;border:1px solid var(--border);border-radius:0;background:var(--surface);color:var(--muted);font-size:10px;font-weight:600;padding:4px 8px;cursor:pointer;font-family:inherit}.pp-filter-chip--active{border-color:var(--primary);background:var(--primary-soft);color:var(--primary)}.pp-group-label{margin:4px 0 0;font-size:10px;font-weight:700;letter-spacing:.03em}.pp-group-label--high{color:var(--danger)}.pp-group-label--medium{color:#d97706}.pp-group-label--low{color:var(--success)}.pp-list{flex:1;overflow:auto;padding:8px 12px 12px;display:flex;flex-direction:column;gap:5px}.pp-priority{flex-shrink:0;width:18px;padding-top:2px;display:inline-flex;align-items:flex-start;justify-content:center;font-size:12px;font-weight:700}.pp-priority--high{color:#e11d48}.pp-priority--medium{color:#eab308}.pp-priority--low{color:#22c55e}.pp-priority--muted{opacity:.55}.pp-row{position:relative;display:flex;gap:6px;align-items:flex-start;padding:9px 10px;background:var(--surface);border:1px solid var(--border);border-radius:0}.pp-row--pending{border-left-width:3px}.pp-row--high{border-left-color:var(--danger)}.pp-row--medium{border-left-color:#eab308}.pp-row--low{border-left-color:#22c55e}.pp-row--purchased{opacity:.95;background:#fafbfc}.pp-row--clickable{cursor:pointer;transition:border-color .15s ease,background .15s ease}.pp-row--clickable:hover{border-color:color-mix(in srgb,var(--primary) 35%,var(--border));background:#f8fafc}.pp-row--clickable:focus-visible{outline:2px solid var(--primary);outline-offset:2px}.pp-row--menu-open{z-index:10;overflow:visible}.pp-row--clickable{cursor:pointer;transition:background .15s ease}.pp-row--clickable:hover{background:#f8fafc}.pp-row--clickable:focus-visible{outline:2px solid var(--primary);outline-offset:-2px}.pp-row__body{flex:1;min-width:0}.pp-row__top{display:flex;align-items:baseline;justify-content:space-between;gap:8px;margin-bottom:2px}.pp-row__main{flex:1;min-width:0}.pp-row__title{margin:0;font-size:14px;font-weight:700;line-height:1.25;min-width:0}.pp-row--compact .pp-row__title{font-size:14px}.pp-row__desc{margin:0 0 4px;font-size:12px;color:var(--muted);line-height:1.35;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.pp-row__qty{flex-shrink:0;font-size:12px;color:var(--primary);font-weight:700;white-space:nowrap}.pp-row__note{margin:0 0 4px;font-size:12px;color:var(--muted);line-height:1.35;display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden}.pp-row__meta{margin:4px 0 0;font-size:9px;color:var(--muted-2);min-width:0}.pp-row__comments{display:inline-flex;align-items:center;gap:3px;white-space:nowrap}.pp-row__comments i{font-size:9px}.pp-row__actions-col{display:flex;flex-direction:column;align-items:center;gap:6px;flex-shrink:0}.pp-row__purchase-note{margin:0 0 6px;font-size:13px;color:var(--success);line-height:1.4}.pp-row__purchase-note i{margin-right:4px;font-size:12px}.pp-row__confirm{flex-shrink:0;width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--success);border-radius:0;background:var(--success-soft);color:var(--success);font-size:12px;cursor:pointer;font-family:inherit;-webkit-tap-highlight-color:transparent}.pp-row__confirm:hover{background:var(--success);color:#fff}.pp-row__confirm:disabled{opacity:.6;cursor:not-allowed}.pp-row__menu-wrap{position:relative;flex-shrink:0;align-self:center}.pp-icon-btn--menu{color:var(--muted-2)}.pp-row-menu{position:absolute;top:calc(100% + 4px);right:0;min-width:148px;background:var(--surface);border:1px solid var(--border);border-radius:0;box-shadow:0 8px 24px #0f172a1f;padding:4px;z-index:20}.pp-row-menu__item{width:100%;border:none;background:transparent;border-radius:0;padding:8px 10px;display:inline-flex;align-items:center;gap:8px;font-size:12px;font-weight:600;color:var(--text);cursor:pointer;text-align:left;font-family:inherit}.pp-row-menu__item:not(:last-child){border-bottom:1px solid var(--border)}.pp-row-menu__item:hover{background:var(--primary-soft);color:var(--primary)}.pp-row-menu__item--danger{color:var(--danger)}.pp-row-menu__item--danger:hover{background:var(--danger-soft);color:var(--danger)}.pp-row-menu__badge{margin-left:auto;font-size:11px;font-weight:700;padding:2px 7px;border-radius:999px;background:var(--primary-soft);color:var(--primary)}.pp-empty{margin:12px 0;text-align:center;color:var(--muted-2);font-size:14px}.pp-empty--group{margin:8px 0 0;font-size:13px}.pp-add-wrap{flex-shrink:0;padding:12px;padding-bottom:calc(12px + env(safe-area-inset-bottom,0px));background:var(--surface);border-top:1px solid var(--border)}.pp-add{width:100%;border:none;border-radius:0;padding:11px 16px;background:var(--primary);color:#fff;font-size:15px;font-weight:700;display:inline-flex;align-items:center;justify-content:center;gap:8px;cursor:pointer}.pp-add:disabled{opacity:.6;cursor:not-allowed}.pp-overlay{position:fixed;inset:0;z-index:420;background:#0f172a80;display:flex;align-items:center;justify-content:center;padding:16px;padding-top:calc(16px + env(safe-area-inset-top,0px));padding-bottom:calc(16px + env(safe-area-inset-bottom,0px))}.pp-overlay--detail{background:#0f172a61;padding:12px}.pp-confirm{width:100%;max-width:360px;background:var(--surface);border-radius:0;padding:18px 16px}.pp-confirm__title{margin:0 0 8px;font-size:16px}.pp-confirm__body{margin:0 0 16px;color:var(--muted);font-size:14px}.pp-confirm__foot{display:flex;justify-content:flex-end;gap:8px}.pp-btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;border:none;border-radius:10px;padding:9px 16px;font-size:14px;font-weight:600;cursor:pointer}.pp-btn--ghost{background:transparent;color:var(--muted)}.pp-btn--danger{background:var(--danger);color:#fff}.pp-btn--primary{background:var(--primary);color:#fff}.pp-btn--success{background:var(--success-soft);border:1px solid var(--success);color:var(--success)}.pp-btn--block{width:100%}.pp-btn--danger-soft{background:color-mix(in srgb,var(--danger) 10%,var(--surface));border:1px solid color-mix(in srgb,var(--danger) 35%,var(--border));color:var(--danger)}.pp-icon-btn--soft{width:34px;height:34px;border-radius:10px;background:var(--surface-2, #f1f5f9);color:var(--muted)}.pp-detail{width:100%;max-width:420px;max-height:min(90vh,720px);background:var(--surface);border:1px solid var(--border);border-radius:16px;display:flex;flex-direction:column;overflow:hidden}.pp-detail__hero{padding:16px 16px 12px;border-bottom:1px solid color-mix(in srgb,var(--border) 70%,transparent)}.pp-detail__hero-top{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:10px}.pp-detail__priority{display:inline-flex;align-items:center;gap:6px;padding:4px 10px;border-radius:999px;font-size:11px;font-weight:700;background:var(--surface-2, #f1f5f9);color:var(--muted)}.pp-detail__priority--high{background:color-mix(in srgb,var(--danger) 12%,white);color:var(--danger)}.pp-detail__priority--medium{background:color-mix(in srgb,#d97706 12%,white);color:#d97706}.pp-detail__priority--low{background:color-mix(in srgb,var(--success) 12%,white);color:var(--success)}.pp-detail__priority .fa-solid{font-size:10px}.pp-detail__name{margin:0 0 8px;font-size:20px;font-weight:800;line-height:1.25;letter-spacing:-.02em;color:var(--text);overflow-wrap:anywhere}.pp-detail__meta{margin:0;display:flex;flex-wrap:wrap;gap:12px;font-size:12px;font-weight:600;color:var(--muted)}.pp-detail__meta span{display:inline-flex;align-items:center;gap:6px}.pp-detail__meta .fa-solid{font-size:11px;opacity:.75}.pp-detail__body{flex:1;overflow:auto;padding:14px 16px 16px;display:flex;flex-direction:column;gap:10px}.pp-detail__foot{display:flex;flex-direction:column;gap:8px;padding:12px 16px 16px;border-top:1px solid color-mix(in srgb,var(--border) 70%,transparent);background:color-mix(in srgb,var(--surface-2, #f8fafc) 50%,var(--surface))}.pp-detail__foot-row{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}.pp-detail__foot-row .pp-btn{width:100%;padding:9px 8px;font-size:13px}.pp-detail-card{padding:12px 14px;border-radius:14px;background:var(--surface-2, #f8fafc);border:1px solid color-mix(in srgb,var(--border) 80%,transparent)}.pp-detail-card__title{margin:0 0 8px;font-size:12px;font-weight:700;color:var(--muted)}.pp-detail-card__text{margin:0;font-size:13px;line-height:1.5;color:var(--text);overflow-wrap:anywhere}.pp-detail-kv{margin:0}.pp-detail-kv__row{display:flex;align-items:baseline;justify-content:space-between;gap:12px;padding:5px 0}.pp-detail-kv__row:not(:last-child){border-bottom:1px solid color-mix(in srgb,var(--border) 55%,transparent)}.pp-detail-kv__row dt{margin:0;font-size:12px;font-weight:500;color:var(--muted)}.pp-detail-kv__row dd{margin:0;font-size:13px;font-weight:700;color:var(--text);text-align:right;overflow-wrap:anywhere}.pp-detail-kv__value--overdue{color:var(--danger)}.pp-comments{margin-top:4px}.pp-comments__head{display:flex;align-items:center;gap:8px;margin-bottom:10px}.pp-comments__title{margin:0;font-size:13px;font-weight:700;color:var(--text)}.pp-comments__count{font-size:11px;font-weight:700;min-width:22px;height:22px;padding:0 7px;display:inline-flex;align-items:center;justify-content:center;border-radius:999px;background:var(--primary-soft);color:var(--primary)}.pp-comments__list{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:8px}.pp-comments__empty{font-size:12px;line-height:1.45;color:var(--muted-2);padding:10px 12px;border-radius:12px;background:var(--surface-2, #f8fafc);border:1px dashed color-mix(in srgb,var(--border) 90%,transparent)}.pp-comments__composer{display:flex;align-items:flex-end;gap:8px;margin-top:12px}.pp-comments__input{flex:1;min-width:0;resize:vertical;min-height:44px;padding:10px 12px;border:1px solid color-mix(in srgb,var(--border) 90%,transparent);border-radius:12px;background:var(--surface);color:var(--text);font-size:13px;font-family:inherit;outline:none;line-height:1.4}.pp-comments__input:focus{border-color:var(--primary)}.pp-comments__send{flex-shrink:0;width:40px;height:40px;display:inline-flex;align-items:center;justify-content:center;border:none;border-radius:12px;background:var(--primary);color:#fff;font-size:14px;cursor:pointer;font-family:inherit;-webkit-tap-highlight-color:transparent}.pp-comments__send:hover:not(:disabled){filter:brightness(1.05)}.pp-comments__send:disabled{opacity:.5;cursor:not-allowed}.pp-comment{border-radius:12px;background:var(--surface-2, #f8fafc);border:1px solid color-mix(in srgb,var(--border) 80%,transparent);padding:10px 12px}.pp-comment__head{display:flex;align-items:baseline;justify-content:space-between;gap:8px;margin-bottom:6px}.pp-comment__author{font-size:12px;font-weight:700;color:var(--text)}.pp-comment__date{font-size:10px;font-weight:600;color:var(--muted-2);font-variant-numeric:tabular-nums}.pp-comment__text{margin:0;font-size:13px;color:var(--muted);line-height:1.45;overflow-wrap:anywhere;white-space:pre-wrap}.pp-comment__link{color:var(--primary);text-decoration:underline;text-underline-offset:2px;word-break:break-all}.pp-comment__link:hover{filter:brightness(.92)}.pp-purchase-entry{border:1px solid var(--border);background:#fafbfc;padding:12px;margin-bottom:10px}.pp-purchase-entry:last-child{margin-bottom:0}.pp-purchase-entry__head{display:flex;flex-wrap:wrap;align-items:baseline;gap:6px 10px;margin-bottom:8px}.pp-purchase-entry__index{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.03em;color:var(--primary)}.pp-purchase-entry__date{font-size:13px;font-weight:700;color:var(--text)}.pp-purchase-entry__qty{margin-left:auto;font-size:12px;font-weight:700;color:var(--muted)}.pp-purchase-entry__note{margin:0 0 10px;font-size:12px;line-height:1.4;color:var(--muted);overflow-wrap:anywhere}.pp-comments--nested{margin-top:0;padding-top:0;border-top:none}.pp-history-sheet{width:min(100%,520px);max-height:min(92vh,720px);margin:0 auto;background:var(--surface);border-radius:0;display:flex;flex-direction:column;overflow:hidden}.pp-history-sheet__head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding:16px;border-bottom:1px solid var(--border)}.pp-history-sheet__title{margin:0;font-size:17px;font-weight:700}.pp-history-sheet__sub{margin:4px 0 0;font-size:13px;color:var(--muted)}.pp-history-sheet__body{flex:1;overflow:auto;padding:12px 16px}.pp-history-sheet__foot{display:flex;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid var(--border)}.pp-history-table{width:100%;border-collapse:collapse;font-size:13px}.pp-history-table th,.pp-history-table td{padding:10px 8px;text-align:left;border-bottom:1px solid var(--border);vertical-align:top}.pp-history-table th{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.03em;color:var(--muted-2);background:#f8fafc;position:sticky;top:0}.pp-history-table tbody tr:last-child td{border-bottom:none}.pp-history-table td:first-child{width:36px;color:var(--muted-2);font-weight:700}.pp-history-table td:nth-child(3){white-space:nowrap}\n"] }]
|
|
23461
23751
|
}], ctorParameters: () => [], propDecorators: { user: [{ type: i0.Input, args: [{ isSignal: true, alias: "user", required: false }] }], back: [{ type: i0.Output, args: ["back"] }], pendingCountChange: [{ type: i0.Output, args: ["pendingCountChange"] }], onDocumentClick: [{
|
|
23462
23752
|
type: HostListener,
|
|
23463
23753
|
args: ['document:click']
|