@dealpos-npm/ng-dealpos-library 21.0.39 → 21.0.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/dealpos-npm-ng-dealpos-library-components-modal-news.mjs +194 -0
- package/fesm2022/dealpos-npm-ng-dealpos-library-components-modal-news.mjs.map +1 -0
- package/fesm2022/dealpos-npm-ng-dealpos-library-dexie-data-access.mjs +112 -6
- package/fesm2022/dealpos-npm-ng-dealpos-library-dexie-data-access.mjs.map +1 -1
- package/package.json +7 -2
- package/types/dealpos-npm-ng-dealpos-library-components-modal-news.d.ts +99 -0
- package/types/dealpos-npm-ng-dealpos-library-dexie-data-access.d.ts +52 -14
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { inject, computed, signal, HostListener, ChangeDetectionStrategy, Component, Injectable } from '@angular/core';
|
|
3
|
+
import { MAT_DIALOG_DATA, MatDialogRef, MatDialog } from '@angular/material/dialog';
|
|
4
|
+
import { NewlinesPipe } from '@dealpos-npm/ng-dealpos-library/pipes/newlines';
|
|
5
|
+
import { BrokenImageIconFailSafeDirective } from '@dealpos-npm/ng-dealpos-library/directives/broken-image-fail-save';
|
|
6
|
+
import { Router, NavigationEnd } from '@angular/router';
|
|
7
|
+
import { filter } from 'rxjs';
|
|
8
|
+
|
|
9
|
+
const DEFAULT_LABELS = {
|
|
10
|
+
close: 'Close',
|
|
11
|
+
prev: 'Previous',
|
|
12
|
+
next: 'Next',
|
|
13
|
+
gotIt: 'Got it',
|
|
14
|
+
goToNews: 'Go to news',
|
|
15
|
+
dialogLabel: 'New feature announcement',
|
|
16
|
+
};
|
|
17
|
+
const SWIPE_THRESHOLD = 40;
|
|
18
|
+
class ModalNewsComponent {
|
|
19
|
+
constructor() {
|
|
20
|
+
this.data = inject(MAT_DIALOG_DATA);
|
|
21
|
+
this.dialogRef = inject(MatDialogRef);
|
|
22
|
+
this.labels = computed(() => ({ ...DEFAULT_LABELS, ...this.data.labels }), ...(ngDevMode ? [{ debugName: "labels" }] : []));
|
|
23
|
+
this.index = signal(this.data.startIndex ?? 0, ...(ngDevMode ? [{ debugName: "index" }] : []));
|
|
24
|
+
this.total = computed(() => this.data.items.length, ...(ngDevMode ? [{ debugName: "total" }] : []));
|
|
25
|
+
this.isMulti = computed(() => this.data.items.length > 1, ...(ngDevMode ? [{ debugName: "isMulti" }] : []));
|
|
26
|
+
this.item = computed(() => this.data.items[this.index()] ?? null, ...(ngDevMode ? [{ debugName: "item" }] : []));
|
|
27
|
+
this.touchStartX = 0;
|
|
28
|
+
this.titleId = 'lib-modal-news-title';
|
|
29
|
+
}
|
|
30
|
+
next() {
|
|
31
|
+
this.move(1);
|
|
32
|
+
}
|
|
33
|
+
prev() {
|
|
34
|
+
this.move(-1);
|
|
35
|
+
}
|
|
36
|
+
goTo(i) {
|
|
37
|
+
this.move(i - this.index());
|
|
38
|
+
}
|
|
39
|
+
dismiss() {
|
|
40
|
+
const item = this.item();
|
|
41
|
+
if (!item) {
|
|
42
|
+
this.close();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (this.index() < this.total() - 1) {
|
|
46
|
+
this.move(1);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
this.dialogRef.close({ action: 'dismiss', index: this.index(), item });
|
|
50
|
+
}
|
|
51
|
+
close() {
|
|
52
|
+
const item = this.item();
|
|
53
|
+
if (!item) {
|
|
54
|
+
this.dialogRef.close();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
this.dialogRef.close({ action: 'closed', index: this.index(), item });
|
|
58
|
+
}
|
|
59
|
+
onAction() {
|
|
60
|
+
const item = this.item();
|
|
61
|
+
if (!item?.actionUrl) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
this.dialogRef.close({ action: 'action', index: this.index(), item, url: item.actionUrl });
|
|
65
|
+
}
|
|
66
|
+
onTouchStart(event) {
|
|
67
|
+
this.touchStartX = event.changedTouches[0]?.clientX ?? 0;
|
|
68
|
+
}
|
|
69
|
+
onTouchEnd(event) {
|
|
70
|
+
const endX = event.changedTouches[0]?.clientX ?? 0;
|
|
71
|
+
const dx = endX - this.touchStartX;
|
|
72
|
+
this.touchStartX = 0;
|
|
73
|
+
if (Math.abs(dx) < SWIPE_THRESHOLD) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
this.move(dx < 0 ? 1 : -1);
|
|
77
|
+
}
|
|
78
|
+
onKeydown(event) {
|
|
79
|
+
if (event.key === 'ArrowRight') {
|
|
80
|
+
event.preventDefault();
|
|
81
|
+
this.move(1);
|
|
82
|
+
}
|
|
83
|
+
else if (event.key === 'ArrowLeft') {
|
|
84
|
+
event.preventDefault();
|
|
85
|
+
this.move(-1);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
move(steps) {
|
|
89
|
+
const target = this.index() + steps;
|
|
90
|
+
if (target < 0 || target >= this.total()) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
this.index.set(target);
|
|
94
|
+
}
|
|
95
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ModalNewsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
96
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.6", type: ModalNewsComponent, isStandalone: true, selector: "lib-modal-news", host: { listeners: { "keydown": "onKeydown($event)" } }, ngImport: i0, template: "<div class=\"news-card\" (touchstart)=\"onTouchStart($event)\" (touchend)=\"onTouchEnd($event)\">\n @if (item(); as news) {\n <div class=\"news-hero\">\n <button\n type=\"button\"\n class=\"btn-close news-close\"\n [attr.aria-label]=\"labels().close\"\n (click)=\"close()\"\n ></button>\n\n @if (isMulti()) {\n <button\n type=\"button\"\n class=\"news-nav news-prev\"\n [disabled]=\"index() === 0\"\n [attr.aria-label]=\"labels().prev\"\n (click)=\"prev()\"\n >\n <i class=\"fa fa-chevron-left\" aria-hidden=\"true\"></i>\n </button>\n <button\n type=\"button\"\n class=\"news-nav news-next\"\n [disabled]=\"index() === total() - 1\"\n [attr.aria-label]=\"labels().next\"\n (click)=\"next()\"\n >\n <i class=\"fa fa-chevron-right\" aria-hidden=\"true\"></i>\n </button>\n }\n\n @if (news.image) {\n <img\n class=\"news-image\"\n [src]=\"news.image\"\n [alt]=\"news.imageAlt || news.title\"\n draggable=\"false\"\n brokenImageIconFailSafe\n (dragstart)=\"$event.preventDefault()\"\n (contextmenu)=\"$event.preventDefault()\"\n />\n } @else {\n <div class=\"news-placeholder\" aria-hidden=\"true\">\n <i class=\"fa fa-image\"></i>\n </div>\n }\n\n @if (isMulti()) {\n <div class=\"news-indicator\">\n <div class=\"news-dots\">\n @for (dot of data.items; track dot.id; let i = $index) {\n <button\n type=\"button\"\n class=\"news-dot\"\n [class.active]=\"i === index()\"\n [attr.aria-label]=\"labels().goToNews + ' #' + (i + 1)\"\n [attr.aria-current]=\"i === index() ? 'true' : null\"\n (click)=\"goTo(i)\"\n ></button>\n }\n </div>\n <span class=\"news-count\">{{ index() + 1 }} / {{ total() }}</span>\n </div>\n }\n </div>\n\n <div class=\"news-body\">\n <h5 class=\"news-title\" [id]=\"titleId\">{{ news.title }}</h5>\n <p class=\"news-text\" [innerHTML]=\"news.body | newlines\"></p>\n\n <div class=\"news-footer\">\n @if (news.actionLabel && news.actionUrl) {\n <a\n class=\"news-learn\"\n role=\"button\"\n [href]=\"news.actionUrl\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n (click)=\"onAction()\"\n >{{ news.actionLabel }}</a\n >\n }\n <button type=\"button\" class=\"news-got\" (click)=\"dismiss()\">\n @if (isMulti() && index() < total() - 1) {\n {{ labels().next }}\n } @else {\n {{ news.dismissLabel || labels().gotIt }}\n }\n </button>\n </div>\n </div>\n\n <p class=\"visually-hidden\" aria-live=\"polite\">\n {{ index() + 1 }} / {{ total() }} \u2014 {{ news.title }}\n </p>\n }\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block}.news-card{border-radius:16px;overflow:hidden;background:#fff;position:relative}.news-hero{position:relative;background:#fef3e8;padding:28px 24px}.news-close{position:absolute;top:12px;right:12px;z-index:2}.news-nav{position:absolute;top:50%;transform:translateY(-50%);z-index:3;width:34px;height:34px;border:none;border-radius:50%;background:#ffffffeb;color:#343a40;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:background-color .15s ease,opacity .15s ease}.news-nav:hover:not(:disabled){background:#fff}.news-nav:disabled{opacity:.35;cursor:default}.news-prev{left:14px}.news-next{right:14px}.news-indicator{position:absolute;bottom:14px;left:50%;transform:translate(-50%);display:flex;align-items:center;gap:10px;padding:6px 12px;border-radius:999px;background:#0000008c;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);z-index:3}.news-dots{display:flex;align-items:center;gap:6px}.news-dot{width:8px;height:8px;padding:0;border:none;border-radius:999px;background:#ffffff8c;cursor:pointer;transition:width .2s ease,background-color .2s ease}.news-dot.active{width:20px;background:#fff}.news-count{font-size:12px;font-weight:700;color:#fff;white-space:nowrap}.news-image{display:block;width:100%;height:auto;border-radius:14px;background:#fff;pointer-events:none;-webkit-user-select:none;user-select:none;-webkit-touch-callout:none;-webkit-user-drag:none}.news-placeholder{display:flex;align-items:center;justify-content:center;min-height:180px;border:1px dashed #ced4da;border-radius:14px;background:#fff}.news-placeholder i{font-size:28px;color:#adb5bd}.news-body{padding:22px 24px 24px;background:#fff;max-height:60vh;overflow-y:auto}.news-title{margin:0 0 8px;font-size:18px;font-weight:800;color:#111418!important}.news-text{margin:0;font-size:14px;color:#343a40!important}.news-footer{display:flex;align-items:center;justify-content:flex-end;gap:16px;margin-top:18px}.news-learn{color:#1a7cb7;font-weight:700;text-decoration:none;cursor:pointer}.news-learn:hover{text-decoration:underline}.news-got{border:none;cursor:pointer;border-radius:999px!important;padding:9px 26px!important;background:#1e88e5!important;color:#fff!important;font-weight:700}.news-got:hover{background:#1976d2!important}.visually-hidden{position:absolute!important;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}::ng-deep .news-modal-panel .mat-mdc-dialog-container{padding:0}::ng-deep .news-modal-panel .mdc-dialog__surface{padding:0;border-radius:16px;overflow:hidden;max-width:475px;background:transparent;box-shadow:0 18px 48px #00000047}::ng-deep .news-modal-backdrop{background:#000;opacity:.5}@media(max-width:768px){.news-body{max-height:52vh;padding:18px 18px 20px}.news-hero{padding:22px 16px}.news-indicator{bottom:10px}}@media(prefers-reduced-motion:reduce){.news-nav,.news-dot{transition:none!important}}\n"], dependencies: [{ kind: "directive", type: BrokenImageIconFailSafeDirective, selector: "[brokenImageIconFailSafe]", inputs: ["linkTarget", "brokenImageIconFailSafeClass", "redirectUrl"] }, { kind: "pipe", type: NewlinesPipe, name: "newlines" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
97
|
+
}
|
|
98
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: ModalNewsComponent, decorators: [{
|
|
99
|
+
type: Component,
|
|
100
|
+
args: [{ selector: 'lib-modal-news', standalone: true, imports: [NewlinesPipe, BrokenImageIconFailSafeDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"news-card\" (touchstart)=\"onTouchStart($event)\" (touchend)=\"onTouchEnd($event)\">\n @if (item(); as news) {\n <div class=\"news-hero\">\n <button\n type=\"button\"\n class=\"btn-close news-close\"\n [attr.aria-label]=\"labels().close\"\n (click)=\"close()\"\n ></button>\n\n @if (isMulti()) {\n <button\n type=\"button\"\n class=\"news-nav news-prev\"\n [disabled]=\"index() === 0\"\n [attr.aria-label]=\"labels().prev\"\n (click)=\"prev()\"\n >\n <i class=\"fa fa-chevron-left\" aria-hidden=\"true\"></i>\n </button>\n <button\n type=\"button\"\n class=\"news-nav news-next\"\n [disabled]=\"index() === total() - 1\"\n [attr.aria-label]=\"labels().next\"\n (click)=\"next()\"\n >\n <i class=\"fa fa-chevron-right\" aria-hidden=\"true\"></i>\n </button>\n }\n\n @if (news.image) {\n <img\n class=\"news-image\"\n [src]=\"news.image\"\n [alt]=\"news.imageAlt || news.title\"\n draggable=\"false\"\n brokenImageIconFailSafe\n (dragstart)=\"$event.preventDefault()\"\n (contextmenu)=\"$event.preventDefault()\"\n />\n } @else {\n <div class=\"news-placeholder\" aria-hidden=\"true\">\n <i class=\"fa fa-image\"></i>\n </div>\n }\n\n @if (isMulti()) {\n <div class=\"news-indicator\">\n <div class=\"news-dots\">\n @for (dot of data.items; track dot.id; let i = $index) {\n <button\n type=\"button\"\n class=\"news-dot\"\n [class.active]=\"i === index()\"\n [attr.aria-label]=\"labels().goToNews + ' #' + (i + 1)\"\n [attr.aria-current]=\"i === index() ? 'true' : null\"\n (click)=\"goTo(i)\"\n ></button>\n }\n </div>\n <span class=\"news-count\">{{ index() + 1 }} / {{ total() }}</span>\n </div>\n }\n </div>\n\n <div class=\"news-body\">\n <h5 class=\"news-title\" [id]=\"titleId\">{{ news.title }}</h5>\n <p class=\"news-text\" [innerHTML]=\"news.body | newlines\"></p>\n\n <div class=\"news-footer\">\n @if (news.actionLabel && news.actionUrl) {\n <a\n class=\"news-learn\"\n role=\"button\"\n [href]=\"news.actionUrl\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n (click)=\"onAction()\"\n >{{ news.actionLabel }}</a\n >\n }\n <button type=\"button\" class=\"news-got\" (click)=\"dismiss()\">\n @if (isMulti() && index() < total() - 1) {\n {{ labels().next }}\n } @else {\n {{ news.dismissLabel || labels().gotIt }}\n }\n </button>\n </div>\n </div>\n\n <p class=\"visually-hidden\" aria-live=\"polite\">\n {{ index() + 1 }} / {{ total() }} \u2014 {{ news.title }}\n </p>\n }\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block}.news-card{border-radius:16px;overflow:hidden;background:#fff;position:relative}.news-hero{position:relative;background:#fef3e8;padding:28px 24px}.news-close{position:absolute;top:12px;right:12px;z-index:2}.news-nav{position:absolute;top:50%;transform:translateY(-50%);z-index:3;width:34px;height:34px;border:none;border-radius:50%;background:#ffffffeb;color:#343a40;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:background-color .15s ease,opacity .15s ease}.news-nav:hover:not(:disabled){background:#fff}.news-nav:disabled{opacity:.35;cursor:default}.news-prev{left:14px}.news-next{right:14px}.news-indicator{position:absolute;bottom:14px;left:50%;transform:translate(-50%);display:flex;align-items:center;gap:10px;padding:6px 12px;border-radius:999px;background:#0000008c;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);z-index:3}.news-dots{display:flex;align-items:center;gap:6px}.news-dot{width:8px;height:8px;padding:0;border:none;border-radius:999px;background:#ffffff8c;cursor:pointer;transition:width .2s ease,background-color .2s ease}.news-dot.active{width:20px;background:#fff}.news-count{font-size:12px;font-weight:700;color:#fff;white-space:nowrap}.news-image{display:block;width:100%;height:auto;border-radius:14px;background:#fff;pointer-events:none;-webkit-user-select:none;user-select:none;-webkit-touch-callout:none;-webkit-user-drag:none}.news-placeholder{display:flex;align-items:center;justify-content:center;min-height:180px;border:1px dashed #ced4da;border-radius:14px;background:#fff}.news-placeholder i{font-size:28px;color:#adb5bd}.news-body{padding:22px 24px 24px;background:#fff;max-height:60vh;overflow-y:auto}.news-title{margin:0 0 8px;font-size:18px;font-weight:800;color:#111418!important}.news-text{margin:0;font-size:14px;color:#343a40!important}.news-footer{display:flex;align-items:center;justify-content:flex-end;gap:16px;margin-top:18px}.news-learn{color:#1a7cb7;font-weight:700;text-decoration:none;cursor:pointer}.news-learn:hover{text-decoration:underline}.news-got{border:none;cursor:pointer;border-radius:999px!important;padding:9px 26px!important;background:#1e88e5!important;color:#fff!important;font-weight:700}.news-got:hover{background:#1976d2!important}.visually-hidden{position:absolute!important;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}::ng-deep .news-modal-panel .mat-mdc-dialog-container{padding:0}::ng-deep .news-modal-panel .mdc-dialog__surface{padding:0;border-radius:16px;overflow:hidden;max-width:475px;background:transparent;box-shadow:0 18px 48px #00000047}::ng-deep .news-modal-backdrop{background:#000;opacity:.5}@media(max-width:768px){.news-body{max-height:52vh;padding:18px 18px 20px}.news-hero{padding:22px 16px}.news-indicator{bottom:10px}}@media(prefers-reduced-motion:reduce){.news-nav,.news-dot{transition:none!important}}\n"] }]
|
|
101
|
+
}], propDecorators: { onKeydown: [{
|
|
102
|
+
type: HostListener,
|
|
103
|
+
args: ['keydown', ['$event']]
|
|
104
|
+
}] } });
|
|
105
|
+
|
|
106
|
+
class NewsModalService {
|
|
107
|
+
constructor() {
|
|
108
|
+
this.dialog = inject(MatDialog);
|
|
109
|
+
}
|
|
110
|
+
open(items, options) {
|
|
111
|
+
return this.dialog.open(ModalNewsComponent, {
|
|
112
|
+
data: { items, startIndex: options?.startIndex ?? 0, labels: options?.labels },
|
|
113
|
+
panelClass: 'news-modal-panel',
|
|
114
|
+
backdropClass: 'news-modal-backdrop',
|
|
115
|
+
width: '475px',
|
|
116
|
+
maxWidth: '95vw',
|
|
117
|
+
autoFocus: 'dialog',
|
|
118
|
+
restoreFocus: true,
|
|
119
|
+
ariaLabel: options?.labels?.dialogLabel ?? 'New feature announcement',
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NewsModalService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
123
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NewsModalService, providedIn: 'root' }); }
|
|
124
|
+
}
|
|
125
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NewsModalService, decorators: [{
|
|
126
|
+
type: Injectable,
|
|
127
|
+
args: [{ providedIn: 'root' }]
|
|
128
|
+
}] });
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Case-insensitive, per-segment prefix match between a router URL and a target route.
|
|
132
|
+
* Query string and fragment are ignored. An empty target matches everything.
|
|
133
|
+
*/
|
|
134
|
+
function matchesNewsTarget(currentUrl, target) {
|
|
135
|
+
const segments = (value) => value
|
|
136
|
+
.split(/[?#]/, 1)[0]
|
|
137
|
+
.toLowerCase()
|
|
138
|
+
.split('/')
|
|
139
|
+
.filter((part) => part.length > 0);
|
|
140
|
+
const current = segments(currentUrl);
|
|
141
|
+
const wanted = segments(target);
|
|
142
|
+
if (!wanted.length)
|
|
143
|
+
return true;
|
|
144
|
+
if (wanted.length > current.length)
|
|
145
|
+
return false;
|
|
146
|
+
return wanted.every((segment, index) => current[index] === segment);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Opens the news modal automatically when the current route matches the `targets` of any
|
|
151
|
+
* supplied item. Persisting "already seen" state is deliberately NOT handled here — the host
|
|
152
|
+
* owns that, and can pass a getter that returns only items the user has not seen yet.
|
|
153
|
+
*/
|
|
154
|
+
class NewsModalRouterService {
|
|
155
|
+
constructor() {
|
|
156
|
+
this.router = inject(Router);
|
|
157
|
+
this.newsModal = inject(NewsModalService);
|
|
158
|
+
this.dialogOpen = signal(false, ...(ngDevMode ? [{ debugName: "dialogOpen" }] : []));
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Evaluates the items against the current URL immediately and again on every navigation.
|
|
162
|
+
* Returns a teardown function that stops watching; it does not close an open dialog.
|
|
163
|
+
*/
|
|
164
|
+
autoOpen(source, options) {
|
|
165
|
+
const resolve = () => (typeof source === 'function' ? source() : source);
|
|
166
|
+
const evaluate = () => {
|
|
167
|
+
if (this.dialogOpen())
|
|
168
|
+
return;
|
|
169
|
+
const url = this.router.url;
|
|
170
|
+
const matched = resolve().filter((item) => !item.targets?.length || item.targets.some((target) => matchesNewsTarget(url, target)));
|
|
171
|
+
if (!matched.length)
|
|
172
|
+
return;
|
|
173
|
+
this.dialogOpen.set(true);
|
|
174
|
+
options?.onOpen?.(matched);
|
|
175
|
+
this.newsModal.open(matched, options).afterClosed().subscribe(() => this.dialogOpen.set(false));
|
|
176
|
+
};
|
|
177
|
+
evaluate();
|
|
178
|
+
const subscription = this.router.events.pipe(filter((event) => event instanceof NavigationEnd)).subscribe(evaluate);
|
|
179
|
+
return () => subscription.unsubscribe();
|
|
180
|
+
}
|
|
181
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NewsModalRouterService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
|
|
182
|
+
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NewsModalRouterService, providedIn: 'root' }); }
|
|
183
|
+
}
|
|
184
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImport: i0, type: NewsModalRouterService, decorators: [{
|
|
185
|
+
type: Injectable,
|
|
186
|
+
args: [{ providedIn: 'root' }]
|
|
187
|
+
}] });
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Generated bundle index. Do not edit.
|
|
191
|
+
*/
|
|
192
|
+
|
|
193
|
+
export { ModalNewsComponent, NewsModalRouterService, NewsModalService };
|
|
194
|
+
//# sourceMappingURL=dealpos-npm-ng-dealpos-library-components-modal-news.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dealpos-npm-ng-dealpos-library-components-modal-news.mjs","sources":["../../../projects/dealpos-npm/ng-dealpos-library/components/modal-news/src/news-modal.component.ts","../../../projects/dealpos-npm/ng-dealpos-library/components/modal-news/src/news-modal.component.html","../../../projects/dealpos-npm/ng-dealpos-library/components/modal-news/src/news-modal.service.ts","../../../projects/dealpos-npm/ng-dealpos-library/components/modal-news/src/news-modal-route.util.ts","../../../projects/dealpos-npm/ng-dealpos-library/components/modal-news/src/news-modal-router.service.ts","../../../projects/dealpos-npm/ng-dealpos-library/components/modal-news/dealpos-npm-ng-dealpos-library-components-modal-news.ts"],"sourcesContent":["import { ChangeDetectionStrategy, Component, HostListener, computed, inject, signal } from '@angular/core';\nimport { MatDialogRef } from '@angular/material/dialog';\nimport { MAT_DIALOG_DATA } from '@angular/material/dialog';\nimport { NewlinesPipe } from '@dealpos-npm/ng-dealpos-library/pipes/newlines';\nimport { BrokenImageIconFailSafeDirective } from '@dealpos-npm/ng-dealpos-library/directives/broken-image-fail-save';\nimport { NewsModalData, NewsModalItem, NewsModalLabels, NewsModalResult } from './news-modal.types';\n\nconst DEFAULT_LABELS: Required<NewsModalLabels> = {\n close: 'Close',\n prev: 'Previous',\n next: 'Next',\n gotIt: 'Got it',\n goToNews: 'Go to news',\n dialogLabel: 'New feature announcement',\n};\n\nconst SWIPE_THRESHOLD = 40;\n\n@Component({\n selector: 'lib-modal-news',\n standalone: true,\n imports: [NewlinesPipe, BrokenImageIconFailSafeDirective],\n templateUrl: './news-modal.component.html',\n styleUrls: ['./news-modal.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ModalNewsComponent {\n readonly data = inject<NewsModalData>(MAT_DIALOG_DATA);\n private readonly dialogRef = inject<MatDialogRef<ModalNewsComponent, NewsModalResult>>(MatDialogRef);\n\n readonly labels = computed<Required<NewsModalLabels>>(() => ({ ...DEFAULT_LABELS, ...this.data.labels }));\n readonly index = signal(this.data.startIndex ?? 0);\n readonly total = computed(() => this.data.items.length);\n readonly isMulti = computed(() => this.data.items.length > 1);\n readonly item = computed<NewsModalItem | null>(() => this.data.items[this.index()] ?? null);\n\n private touchStartX = 0;\n\n readonly titleId = 'lib-modal-news-title';\n\n next(): void {\n this.move(1);\n }\n\n prev(): void {\n this.move(-1);\n }\n\n goTo(i: number): void {\n this.move(i - this.index());\n }\n\n dismiss(): void {\n const item = this.item();\n if (!item) {\n this.close();\n return;\n }\n if (this.index() < this.total() - 1) {\n this.move(1);\n return;\n }\n this.dialogRef.close({ action: 'dismiss', index: this.index(), item });\n }\n\n close(): void {\n const item = this.item();\n if (!item) {\n this.dialogRef.close();\n return;\n }\n this.dialogRef.close({ action: 'closed', index: this.index(), item });\n }\n\n onAction(): void {\n const item = this.item();\n if (!item?.actionUrl) {\n return;\n }\n this.dialogRef.close({ action: 'action', index: this.index(), item, url: item.actionUrl });\n }\n\n onTouchStart(event: TouchEvent): void {\n this.touchStartX = event.changedTouches[0]?.clientX ?? 0;\n }\n\n onTouchEnd(event: TouchEvent): void {\n const endX = event.changedTouches[0]?.clientX ?? 0;\n const dx = endX - this.touchStartX;\n this.touchStartX = 0;\n if (Math.abs(dx) < SWIPE_THRESHOLD) {\n return;\n }\n this.move(dx < 0 ? 1 : -1);\n }\n\n @HostListener('keydown', ['$event'])\n onKeydown(event: KeyboardEvent): void {\n if (event.key === 'ArrowRight') {\n event.preventDefault();\n this.move(1);\n } else if (event.key === 'ArrowLeft') {\n event.preventDefault();\n this.move(-1);\n }\n }\n\n private move(steps: number): void {\n const target = this.index() + steps;\n if (target < 0 || target >= this.total()) {\n return;\n }\n this.index.set(target);\n }\n}\n","<div class=\"news-card\" (touchstart)=\"onTouchStart($event)\" (touchend)=\"onTouchEnd($event)\">\n @if (item(); as news) {\n <div class=\"news-hero\">\n <button\n type=\"button\"\n class=\"btn-close news-close\"\n [attr.aria-label]=\"labels().close\"\n (click)=\"close()\"\n ></button>\n\n @if (isMulti()) {\n <button\n type=\"button\"\n class=\"news-nav news-prev\"\n [disabled]=\"index() === 0\"\n [attr.aria-label]=\"labels().prev\"\n (click)=\"prev()\"\n >\n <i class=\"fa fa-chevron-left\" aria-hidden=\"true\"></i>\n </button>\n <button\n type=\"button\"\n class=\"news-nav news-next\"\n [disabled]=\"index() === total() - 1\"\n [attr.aria-label]=\"labels().next\"\n (click)=\"next()\"\n >\n <i class=\"fa fa-chevron-right\" aria-hidden=\"true\"></i>\n </button>\n }\n\n @if (news.image) {\n <img\n class=\"news-image\"\n [src]=\"news.image\"\n [alt]=\"news.imageAlt || news.title\"\n draggable=\"false\"\n brokenImageIconFailSafe\n (dragstart)=\"$event.preventDefault()\"\n (contextmenu)=\"$event.preventDefault()\"\n />\n } @else {\n <div class=\"news-placeholder\" aria-hidden=\"true\">\n <i class=\"fa fa-image\"></i>\n </div>\n }\n\n @if (isMulti()) {\n <div class=\"news-indicator\">\n <div class=\"news-dots\">\n @for (dot of data.items; track dot.id; let i = $index) {\n <button\n type=\"button\"\n class=\"news-dot\"\n [class.active]=\"i === index()\"\n [attr.aria-label]=\"labels().goToNews + ' #' + (i + 1)\"\n [attr.aria-current]=\"i === index() ? 'true' : null\"\n (click)=\"goTo(i)\"\n ></button>\n }\n </div>\n <span class=\"news-count\">{{ index() + 1 }} / {{ total() }}</span>\n </div>\n }\n </div>\n\n <div class=\"news-body\">\n <h5 class=\"news-title\" [id]=\"titleId\">{{ news.title }}</h5>\n <p class=\"news-text\" [innerHTML]=\"news.body | newlines\"></p>\n\n <div class=\"news-footer\">\n @if (news.actionLabel && news.actionUrl) {\n <a\n class=\"news-learn\"\n role=\"button\"\n [href]=\"news.actionUrl\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n (click)=\"onAction()\"\n >{{ news.actionLabel }}</a\n >\n }\n <button type=\"button\" class=\"news-got\" (click)=\"dismiss()\">\n @if (isMulti() && index() < total() - 1) {\n {{ labels().next }}\n } @else {\n {{ news.dismissLabel || labels().gotIt }}\n }\n </button>\n </div>\n </div>\n\n <p class=\"visually-hidden\" aria-live=\"polite\">\n {{ index() + 1 }} / {{ total() }} — {{ news.title }}\n </p>\n }\n</div>\n","import { inject, Injectable } from '@angular/core';\nimport { MatDialog, MatDialogRef } from '@angular/material/dialog';\nimport { ModalNewsComponent } from './news-modal.component';\nimport { NewsModalData, NewsModalItem, NewsModalLabels, NewsModalResult } from './news-modal.types';\n\nexport interface NewsModalOpenOptions {\n startIndex?: number;\n labels?: NewsModalLabels;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class NewsModalService {\n private readonly dialog = inject(MatDialog);\n\n open(\n items: NewsModalItem[],\n options?: NewsModalOpenOptions,\n ): MatDialogRef<ModalNewsComponent, NewsModalResult | undefined> {\n return this.dialog.open<ModalNewsComponent, NewsModalData, NewsModalResult | undefined>(ModalNewsComponent, {\n data: { items, startIndex: options?.startIndex ?? 0, labels: options?.labels },\n panelClass: 'news-modal-panel',\n backdropClass: 'news-modal-backdrop',\n width: '475px',\n maxWidth: '95vw',\n autoFocus: 'dialog',\n restoreFocus: true,\n ariaLabel: options?.labels?.dialogLabel ?? 'New feature announcement',\n });\n }\n}\n","/**\n * Case-insensitive, per-segment prefix match between a router URL and a target route.\n * Query string and fragment are ignored. An empty target matches everything.\n */\nexport function matchesNewsTarget(currentUrl: string, target: string): boolean {\n const segments = (value: string): string[] =>\n value\n .split(/[?#]/, 1)[0]\n .toLowerCase()\n .split('/')\n .filter((part) => part.length > 0);\n\n const current = segments(currentUrl);\n const wanted = segments(target);\n if (!wanted.length) return true;\n if (wanted.length > current.length) return false;\n return wanted.every((segment, index) => current[index] === segment);\n}\n","import { Injectable, inject, signal } from '@angular/core';\nimport { NavigationEnd, Router } from '@angular/router';\nimport { filter } from 'rxjs';\nimport { NewsModalItem, NewsModalLabels } from './news-modal.types';\nimport { NewsModalService } from './news-modal.service';\nimport { matchesNewsTarget } from './news-modal-route.util';\n\nexport interface NewsModalAutoOpenOptions {\n startIndex?: number;\n labels?: NewsModalLabels;\n onOpen?: (items: NewsModalItem[]) => void;\n}\n\n/**\n * Opens the news modal automatically when the current route matches the `targets` of any\n * supplied item. Persisting \"already seen\" state is deliberately NOT handled here — the host\n * owns that, and can pass a getter that returns only items the user has not seen yet.\n */\n@Injectable({ providedIn: 'root' })\nexport class NewsModalRouterService {\n private readonly router = inject(Router);\n private readonly newsModal = inject(NewsModalService);\n private readonly dialogOpen = signal(false);\n\n /**\n * Evaluates the items against the current URL immediately and again on every navigation.\n * Returns a teardown function that stops watching; it does not close an open dialog.\n */\n autoOpen(\n source: NewsModalItem[] | (() => NewsModalItem[]),\n options?: NewsModalAutoOpenOptions,\n ): () => void {\n const resolve = (): NewsModalItem[] => (typeof source === 'function' ? source() : source);\n\n const evaluate = (): void => {\n if (this.dialogOpen()) return;\n const url = this.router.url;\n const matched = resolve().filter(\n (item) => !item.targets?.length || item.targets.some((target) => matchesNewsTarget(url, target)),\n );\n if (!matched.length) return;\n this.dialogOpen.set(true);\n options?.onOpen?.(matched);\n this.newsModal.open(matched, options).afterClosed().subscribe(() => this.dialogOpen.set(false));\n };\n\n evaluate();\n const subscription = this.router.events.pipe(filter((event) => event instanceof NavigationEnd)).subscribe(evaluate);\n\n return () => subscription.unsubscribe();\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,7 @@ import Dexie, { liveQuery } from 'dexie';
|
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
3
|
import { InjectionToken, Inject, Injectable, makeEnvironmentProviders } from '@angular/core';
|
|
4
4
|
import { Observable } from 'rxjs';
|
|
5
|
+
import { v4 } from 'uuid';
|
|
5
6
|
import Fuse from 'fuse.js';
|
|
6
7
|
|
|
7
8
|
class PosDatabaseBase extends Dexie {
|
|
@@ -12,6 +13,14 @@ class PosDatabaseBase extends Dexie {
|
|
|
12
13
|
orders: '++id, status',
|
|
13
14
|
settings: 'key',
|
|
14
15
|
});
|
|
16
|
+
// v2 replaces the auto-increment `orders` primary key with a UUID string.
|
|
17
|
+
// The schema change already drops the object store, so this explicit clear
|
|
18
|
+
// only states the intent: old numeric-key order rows are not migrated.
|
|
19
|
+
this.version(2)
|
|
20
|
+
.stores({
|
|
21
|
+
orders: 'id, status',
|
|
22
|
+
})
|
|
23
|
+
.upgrade((tx) => tx.table('orders').clear());
|
|
15
24
|
}
|
|
16
25
|
}
|
|
17
26
|
|
|
@@ -30,8 +39,14 @@ class OrderQueueRepository {
|
|
|
30
39
|
toStored(record) {
|
|
31
40
|
return Object.assign(record.order, { id: record.id });
|
|
32
41
|
}
|
|
33
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Queues an order. Pass `id` to make the save idempotent — re-saving the same
|
|
44
|
+
* order overwrites the existing record instead of creating a duplicate.
|
|
45
|
+
* When omitted, a fresh UUID is generated.
|
|
46
|
+
*/
|
|
47
|
+
saveUnsent(order, id) {
|
|
34
48
|
return this.table.add({
|
|
49
|
+
id: id ?? v4(),
|
|
35
50
|
status: 'unsent',
|
|
36
51
|
order: this.toPayload(order),
|
|
37
52
|
});
|
|
@@ -104,20 +119,91 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.6", ngImpor
|
|
|
104
119
|
args: [POS_DATABASE]
|
|
105
120
|
}] }] });
|
|
106
121
|
|
|
122
|
+
const UNRANKED = Number.MAX_SAFE_INTEGER;
|
|
123
|
+
function escapeContainsToken(token) {
|
|
124
|
+
return token.replace(/"/g, '').replace(/\|/g, '\\|');
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Builds an extended-search pattern that matches by **substring** ("contains")
|
|
128
|
+
* instead of the default fuzzy bitap algorithm. Each word becomes one `include`
|
|
129
|
+
* matcher (`'word`) and words are AND-ed because extended search treats
|
|
130
|
+
* space-separated tokens as a conjunction.
|
|
131
|
+
*/
|
|
132
|
+
function toContainsPattern(query) {
|
|
133
|
+
return query
|
|
134
|
+
.trim()
|
|
135
|
+
.split(/\s+/)
|
|
136
|
+
.map(escapeContainsToken)
|
|
137
|
+
.filter((token) => token.length > 0)
|
|
138
|
+
.map((token) => `'${token}`)
|
|
139
|
+
.join(' ');
|
|
140
|
+
}
|
|
141
|
+
function firstMatchIndex(arg) {
|
|
142
|
+
let first = UNRANKED;
|
|
143
|
+
for (const match of arg.matches ?? []) {
|
|
144
|
+
const start = match.indices?.[0]?.[0];
|
|
145
|
+
if (typeof start === 'number' && start < first) {
|
|
146
|
+
first = start;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return first;
|
|
150
|
+
}
|
|
151
|
+
function lastMatchIndex(arg) {
|
|
152
|
+
let last = -1;
|
|
153
|
+
for (const match of arg.matches ?? []) {
|
|
154
|
+
for (const range of match.indices ?? []) {
|
|
155
|
+
if (range[0] > last) {
|
|
156
|
+
last = range[0];
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return last;
|
|
161
|
+
}
|
|
162
|
+
function shortestMatchedValue(arg) {
|
|
163
|
+
let shortest = UNRANKED;
|
|
164
|
+
for (const match of arg.matches ?? []) {
|
|
165
|
+
shortest = Math.min(shortest, match.value?.length ?? 0);
|
|
166
|
+
}
|
|
167
|
+
return shortest;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Substring matches all score 0 in extended search, so relevance is restored
|
|
171
|
+
* here: earliest hit first (prefix beats mid-word), then tightest span, then
|
|
172
|
+
* shortest matched field, then catalog order as the stable tie-break.
|
|
173
|
+
*/
|
|
174
|
+
const matchPositionSortFn = (a, b) => {
|
|
175
|
+
const first = firstMatchIndex(a) - firstMatchIndex(b);
|
|
176
|
+
if (first !== 0) {
|
|
177
|
+
return first;
|
|
178
|
+
}
|
|
179
|
+
const last = lastMatchIndex(a) - lastMatchIndex(b);
|
|
180
|
+
if (last !== 0) {
|
|
181
|
+
return last;
|
|
182
|
+
}
|
|
183
|
+
const length = shortestMatchedValue(a) - shortestMatchedValue(b);
|
|
184
|
+
if (length !== 0) {
|
|
185
|
+
return length;
|
|
186
|
+
}
|
|
187
|
+
return a.idx - b.idx;
|
|
188
|
+
};
|
|
107
189
|
function createFuzzyIndex(items, keys, options) {
|
|
108
190
|
return new Fuse([...items], {
|
|
109
191
|
includeScore: true,
|
|
110
192
|
includeMatches: true,
|
|
193
|
+
findAllMatches: true,
|
|
194
|
+
useExtendedSearch: true,
|
|
195
|
+
sortFn: matchPositionSortFn,
|
|
111
196
|
keys: [...keys],
|
|
112
197
|
...(options ?? {}),
|
|
113
198
|
});
|
|
114
199
|
}
|
|
115
200
|
function fuzzySearch(index, query, opts) {
|
|
116
|
-
|
|
201
|
+
const pattern = index.options.useExtendedSearch !== false ? toContainsPattern(query) : query.trim();
|
|
202
|
+
if (!pattern) {
|
|
117
203
|
return [];
|
|
118
204
|
}
|
|
119
205
|
return index
|
|
120
|
-
.search(
|
|
206
|
+
.search(pattern, opts)
|
|
121
207
|
.map((r) => ({
|
|
122
208
|
item: r.item,
|
|
123
209
|
score: r.score ?? 0,
|
|
@@ -165,6 +251,20 @@ class KeyedTableRepository {
|
|
|
165
251
|
count() {
|
|
166
252
|
return this.table.count();
|
|
167
253
|
}
|
|
254
|
+
/**
|
|
255
|
+
* Estimasi ukuran penyimpanan (byte) untuk seluruh baris di tabel ini.
|
|
256
|
+
* Dihitung dari panjang serialisasi JSON (UTF-8) tiap record, jadi ini
|
|
257
|
+
* hanya perkiraan — belum termasuk overhead structured-clone dan index Dexie.
|
|
258
|
+
* Membaca per baris via `table.each()` agar tidak menahan seluruh tabel di memori.
|
|
259
|
+
*/
|
|
260
|
+
async estimateSize() {
|
|
261
|
+
const encoder = new TextEncoder();
|
|
262
|
+
let total = 0;
|
|
263
|
+
await this.table.each((item) => {
|
|
264
|
+
total += encoder.encode(JSON.stringify(item)).length;
|
|
265
|
+
});
|
|
266
|
+
return total;
|
|
267
|
+
}
|
|
168
268
|
}
|
|
169
269
|
|
|
170
270
|
class ProductsRepository extends KeyedTableRepository {
|
|
@@ -202,8 +302,14 @@ class SettingsRepository extends KeyValueRepository {
|
|
|
202
302
|
getSyncTime() {
|
|
203
303
|
return this.get('syncTime');
|
|
204
304
|
}
|
|
205
|
-
|
|
206
|
-
|
|
305
|
+
/**
|
|
306
|
+
* Catat waktu sync terakhir, opsional beserta durasi proses sync (ms).
|
|
307
|
+
* `durationMs` bersifat opsional agar pemanggil lama tetap kompatibel.
|
|
308
|
+
* Saat tidak diisi, field tidak disimpan sehingga record tetap `{ time }`.
|
|
309
|
+
*/
|
|
310
|
+
putSyncTime(time, durationMs) {
|
|
311
|
+
const value = durationMs === undefined ? { time } : { time, durationMs };
|
|
312
|
+
return this.put('syncTime', value);
|
|
207
313
|
}
|
|
208
314
|
getLoadPOSAPI() {
|
|
209
315
|
return this.get('LoadPOSAPI');
|
|
@@ -242,5 +348,5 @@ function provideDexieDataAccess(database) {
|
|
|
242
348
|
* Generated bundle index. Do not edit.
|
|
243
349
|
*/
|
|
244
350
|
|
|
245
|
-
export { KeyValueRepository, KeyedTableRepository, OrderQueueRepository, OrdersRepository, POS_DATABASE, PosDatabaseBase, ProductsRepository, SettingsRepository, createFuzzyIndex, fuzzySearch, provideDexieDataAccess };
|
|
351
|
+
export { KeyValueRepository, KeyedTableRepository, OrderQueueRepository, OrdersRepository, POS_DATABASE, PosDatabaseBase, ProductsRepository, SettingsRepository, createFuzzyIndex, fuzzySearch, matchPositionSortFn, provideDexieDataAccess, toContainsPattern };
|
|
246
352
|
//# sourceMappingURL=dealpos-npm-ng-dealpos-library-dexie-data-access.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dealpos-npm-ng-dealpos-library-dexie-data-access.mjs","sources":["../../../projects/dealpos-npm/ng-dealpos-library/dexie-data-access/src/db/pos-database.base.ts","../../../projects/dealpos-npm/ng-dealpos-library/dexie-data-access/src/di/pos-database.token.ts","../../../projects/dealpos-npm/ng-dealpos-library/dexie-data-access/src/repositories/order-queue.repository.ts","../../../projects/dealpos-npm/ng-dealpos-library/dexie-data-access/src/repositories/orders.repository.ts","../../../projects/dealpos-npm/ng-dealpos-library/dexie-data-access/src/search/fuzzy-search.ts","../../../projects/dealpos-npm/ng-dealpos-library/dexie-data-access/src/repositories/keyed-table.repository.ts","../../../projects/dealpos-npm/ng-dealpos-library/dexie-data-access/src/repositories/products.repository.ts","../../../projects/dealpos-npm/ng-dealpos-library/dexie-data-access/src/repositories/key-value.repository.ts","../../../projects/dealpos-npm/ng-dealpos-library/dexie-data-access/src/repositories/settings.repository.ts","../../../projects/dealpos-npm/ng-dealpos-library/dexie-data-access/src/di/provide-pos-dexie-data-access.ts","../../../projects/dealpos-npm/ng-dealpos-library/dexie-data-access/dealpos-npm-ng-dealpos-library-dexie-data-access.ts"],"sourcesContent":["import Dexie, { type EntityTable, type IndexableType } from 'dexie';\nimport type {\n\tSalesOrderDataModel,\n\tVariantByIDHttpResponse,\n} from '@dealpos-npm/ng-dealpos-library/sell';\nimport type { OrderRecord, SettingRecord } from './records.model';\n\nexport abstract class PosDatabaseBase<\n\tTProduct extends { ID: IndexableType } = VariantByIDHttpResponse,\n\tTOrder = SalesOrderDataModel,\n> extends Dexie {\n\tproducts!: EntityTable<TProduct, 'ID'>;\n\torders!: EntityTable<OrderRecord<TOrder>, 'id'>;\n\tsettings!: EntityTable<SettingRecord, 'key'>;\n\n\tprotected constructor(name: string) {\n\t\tsuper(name);\n\t\tthis.version(1).stores({\n\t\t\tproducts: 'ID, Code',\n\t\t\torders: '++id, status',\n\t\t\tsettings: 'key',\n\t\t});\n\t}\n}\n","import { InjectionToken } from '@angular/core';\nimport type { PosDatabaseBase } from '../db/pos-database.base';\n\nexport const POS_DATABASE = new InjectionToken<PosDatabaseBase>('POS_DATABASE');\n","import { liveQuery, type EntityTable } from 'dexie';\nimport { Observable } from 'rxjs';\nimport type { SalesOrderDataModel } from '@dealpos-npm/ng-dealpos-library/sell';\nimport type { OrderRecord, OrderStatus } from '../db/records.model';\n\nexport type StoredEntity<T> = T & { id?: number };\n\nexport class OrderQueueRepository<TOrder = SalesOrderDataModel> {\n\tconstructor(\n\t\tprotected readonly table: EntityTable<OrderRecord<TOrder>, 'id', OrderRecord<TOrder>>,\n\t\tprotected readonly options: {\n\t\t\torderIdExtractor: (order: TOrder) => string | number;\n\t\t},\n\t) {}\n\n\tprivate toPayload(order: TOrder): TOrder {\n\t\tconst payload = JSON.parse(JSON.stringify(order)) as TOrder & { id?: number };\n\t\tdelete payload.id;\n\t\treturn payload as TOrder;\n\t}\n\n\tprivate toStored(record: OrderRecord<TOrder>): StoredEntity<TOrder> {\n\t\treturn Object.assign(record.order as object, { id: record.id }) as StoredEntity<TOrder>;\n\t}\n\n\tsaveUnsent(order: TOrder): Promise<number> {\n\t\treturn this.table.add({\n\t\t\tstatus: 'unsent',\n\t\t\torder: this.toPayload(order),\n\t\t}) as Promise<number>;\n\t}\n\n\tobserveByStatus(status: OrderStatus): Observable<Array<StoredEntity<TOrder>>> {\n\t\treturn new Observable((subscriber) => {\n\t\t\tconst live = liveQuery(() => this.table.where('status').equals(status).toArray());\n\t\t\tconst subscription = live.subscribe({\n\t\t\t\tnext: (records) => subscriber.next(records.map((record) => this.toStored(record))),\n\t\t\t\terror: (error) => subscriber.error(error),\n\t\t\t});\n\t\t\treturn () => subscription.unsubscribe();\n\t\t});\n\t}\n\n\tasync listByStatus(status: OrderStatus): Promise<Array<StoredEntity<TOrder>>> {\n\t\tconst records = await this.table.where('status').equals(status).toArray();\n\t\treturn records.map((record) => this.toStored(record));\n\t}\n\n\tlistUnsent(): Promise<Array<StoredEntity<TOrder>>> {\n\t\treturn this.listByStatus('unsent');\n\t}\n\n\tlistFailed(): Promise<Array<StoredEntity<TOrder>>> {\n\t\treturn this.listByStatus('failed');\n\t}\n\n\tlistSuccess(): Promise<Array<StoredEntity<TOrder>>> {\n\t\treturn this.listByStatus('success');\n\t}\n\n\tasync findUnsentByOrderId(\n\t\torderId: string | number,\n\t): Promise<StoredEntity<TOrder> | undefined> {\n\t\tconst records = await this.table.where('status').equals('unsent').toArray();\n\t\tconst match = records.find(\n\t\t\t(record) => this.options.orderIdExtractor(record.order) == orderId,\n\t\t);\n\t\treturn match ? this.toStored(match) : undefined;\n\t}\n\n\tasync remove(id: number): Promise<void> {\n\t\tawait this.table.delete(id);\n\t}\n\n\tmarkFailed(id: number, order: TOrder): Promise<number> {\n\t\treturn this.table.put({\n\t\t\tid,\n\t\t\tstatus: 'failed',\n\t\t\torder: this.toPayload(order),\n\t\t}) as Promise<number>;\n\t}\n\n\tmarkSuccess(id: number, order: TOrder): Promise<number> {\n\t\treturn this.table.put({\n\t\t\tid,\n\t\t\tstatus: 'success',\n\t\t\torder: this.toPayload(order),\n\t\t}) as Promise<number>;\n\t}\n\n\tclearFailed(): Promise<number> {\n\t\treturn this.table.where('status').equals('failed').delete();\n\t}\n\n\tclearSuccess(): Promise<number> {\n\t\treturn this.table.where('status').equals('success').delete();\n\t}\n}\n","import { Inject, Injectable } from '@angular/core';\nimport type { SalesOrderDataModel } from '@dealpos-npm/ng-dealpos-library/sell';\nimport { OrderQueueRepository } from './order-queue.repository';\nimport { POS_DATABASE } from '../di/pos-database.token';\nimport type { PosDatabaseBase } from '../db/pos-database.base';\n\nexport type StoredOrder = SalesOrderDataModel & { id?: number };\n\n@Injectable({ providedIn: 'root' })\nexport class OrdersRepository extends OrderQueueRepository<SalesOrderDataModel> {\n\tconstructor(@Inject(POS_DATABASE) db: PosDatabaseBase) {\n\t\tsuper(db.orders, { orderIdExtractor: (o) => o.Order?.ID });\n\t}\n}\n","import Fuse from 'fuse.js';\nimport type {\n\tFuseOptionKey,\n\tFuseResultMatch,\n\tFuseSearchOptions,\n\tIFuseOptions,\n} from 'fuse.js';\n\nexport interface FuzzyMatch<T> {\n\titem: T;\n\tscore: number;\n\tmatches: readonly FuseResultMatch[];\n}\n\nexport function createFuzzyIndex<T>(\n\titems: ReadonlyArray<T>,\n\tkeys: ReadonlyArray<FuseOptionKey<T>>,\n\toptions?: IFuseOptions<T>,\n): Fuse<T> {\n\treturn new Fuse([...items], {\n\t\tincludeScore: true,\n\t\tincludeMatches: true,\n\t\tkeys: [...keys],\n\t\t...(options ?? {}),\n\t});\n}\n\nexport function fuzzySearch<T>(\n\tindex: Fuse<T>,\n\tquery: string,\n\topts?: FuseSearchOptions,\n): Array<FuzzyMatch<T>> {\n\tif (!query) {\n\t\treturn [];\n\t}\n\treturn index\n\t\t.search(query, opts)\n\t\t.map((r) => ({\n\t\t\titem: r.item,\n\t\t\tscore: r.score ?? 0,\n\t\t\tmatches: r.matches ?? [],\n\t\t}));\n}\n","import type { EntityTable, IDType } from 'dexie';\nimport type { default as Fuse, FuseOptionKey, FuseSearchOptions, IFuseOptions } from 'fuse.js';\nimport { createFuzzyIndex, fuzzySearch } from '../search/fuzzy-search';\nimport type { FuzzyMatch } from '../search/fuzzy-search';\n\nexport class KeyedTableRepository<T, K extends keyof T & string> {\n\tprivate fuzzyIndex: Fuse<T> | undefined;\n\n\tconstructor(\n\t\tprotected readonly table: EntityTable<T, K>,\n\t\tprotected readonly options?: {\n\t\t\tcodeIndex?: string;\n\t\t\tfuzzyKeys?: ReadonlyArray<FuseOptionKey<T>>;\n\t\t\tfuzzyOptions?: IFuseOptions<T>;\n\t\t},\n\t) {}\n\n\tgetAll(): Promise<T[]> {\n\t\treturn this.table.toArray();\n\t}\n\n\tgetById(id: T[K]): Promise<T | undefined> {\n\t\treturn this.table.get(id as IDType<T, K>);\n\t}\n\n\tgetByIndex(index: string, value: unknown): Promise<T | undefined> {\n\t\treturn this.table.where(index).equals(value as never).first();\n\t}\n\n\tasync searchFuzzy(\n\t\tquery: string,\n\t\topts?: FuseSearchOptions,\n\t): Promise<Array<FuzzyMatch<T>>> {\n\t\tconst keys = this.options?.fuzzyKeys;\n\t\tif (!keys || keys.length === 0 || !query) {\n\t\t\treturn [];\n\t\t}\n\t\tif (!this.fuzzyIndex) {\n\t\t\tthis.fuzzyIndex = createFuzzyIndex(\n\t\t\t\tawait this.getAll(),\n\t\t\t\tkeys,\n\t\t\t\tthis.options?.fuzzyOptions,\n\t\t\t);\n\t\t}\n\t\treturn fuzzySearch(this.fuzzyIndex, query, opts);\n\t}\n\n\treplaceAll(items: T[]): Promise<void> {\n\t\tthis.fuzzyIndex = undefined;\n\t\treturn this.table.db.transaction('rw', this.table, async () => {\n\t\t\tawait this.table.clear();\n\t\t\tif (items.length > 0) {\n\t\t\t\tawait this.table.bulkAdd(items);\n\t\t\t}\n\t\t});\n\t}\n\n\tclear(): Promise<void> {\n\t\tthis.fuzzyIndex = undefined;\n\t\treturn this.table.clear();\n\t}\n\n\tcount(): Promise<number> {\n\t\treturn this.table.count();\n\t}\n}\n","import { Inject, Injectable } from '@angular/core';\nimport type { VariantByIDHttpResponse } from '@dealpos-npm/ng-dealpos-library/sell';\nimport { KeyedTableRepository } from './keyed-table.repository';\nimport { POS_DATABASE } from '../di/pos-database.token';\nimport type { PosDatabaseBase } from '../db/pos-database.base';\n\n@Injectable({ providedIn: 'root' })\nexport class ProductsRepository extends KeyedTableRepository<VariantByIDHttpResponse, 'ID'> {\n\tconstructor(@Inject(POS_DATABASE) db: PosDatabaseBase) {\n\t\tsuper(db.products, { codeIndex: 'Code', fuzzyKeys: ['Name', 'Code'] });\n\t}\n}\n","import type { EntityTable } from 'dexie';\nimport type { SettingKey, SettingRecord } from '../db/records.model';\n\nexport class KeyValueRepository<TMap extends object> {\n\tconstructor(protected readonly table: EntityTable<SettingRecord, 'key'>) {}\n\n\tasync get<K extends keyof TMap & string>(key: K): Promise<TMap[K] | undefined> {\n\t\tconst record = await this.table.get(key as SettingKey);\n\t\treturn record?.value as TMap[K] | undefined;\n\t}\n\n\tput<K extends keyof TMap & string>(key: K, value: TMap[K]): Promise<SettingKey> {\n\t\treturn this.table.put({ key: key as SettingKey, value });\n\t}\n}\n","import { Inject, Injectable } from '@angular/core';\nimport { KeyValueRepository } from './key-value.repository';\nimport { POS_DATABASE } from '../di/pos-database.token';\nimport type { PosDatabaseBase } from '../db/pos-database.base';\nimport type { SettingKey, SyncTimeValue } from '../db/records.model';\n\nexport interface CanonicalSettingsMap {\n\tsyncTime: SyncTimeValue;\n\tLoadPOSAPI: unknown;\n\tactiveRegister: unknown;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class SettingsRepository extends KeyValueRepository<CanonicalSettingsMap> {\n\tconstructor(@Inject(POS_DATABASE) db: PosDatabaseBase) {\n\t\tsuper(db.settings);\n\t}\n\n\tgetSyncTime(): Promise<SyncTimeValue | undefined> {\n\t\treturn this.get('syncTime');\n\t}\n\n\tputSyncTime(time: string): Promise<SettingKey> {\n\t\treturn this.put('syncTime', { time });\n\t}\n\n\tgetLoadPOSAPI<T = unknown>(): Promise<T | undefined> {\n\t\treturn this.get('LoadPOSAPI') as Promise<T | undefined>;\n\t}\n\n\tputLoadPOSAPI<T>(value: T): Promise<SettingKey> {\n\t\treturn this.put('LoadPOSAPI', value);\n\t}\n\n\tasync getActiveRegister<T = unknown>(): Promise<T | null> {\n\t\treturn ((await this.get('activeRegister')) as T | undefined) ?? null;\n\t}\n\n\tputActiveRegister<T>(value: T): Promise<SettingKey> {\n\t\treturn this.put('activeRegister', value);\n\t}\n}\n","import { makeEnvironmentProviders } from '@angular/core';\nimport type { EnvironmentProviders, Type } from '@angular/core';\nimport { POS_DATABASE } from './pos-database.token';\nimport type { PosDatabaseBase } from '../db/pos-database.base';\nimport { OrdersRepository } from '../repositories/orders.repository';\nimport { ProductsRepository } from '../repositories/products.repository';\nimport { SettingsRepository } from '../repositories/settings.repository';\n\nexport function provideDexieDataAccess(database: Type<PosDatabaseBase>): EnvironmentProviders {\n\treturn makeEnvironmentProviders([\n\t\tdatabase,\n\t\t{ provide: POS_DATABASE, useExisting: database },\n\t\tOrdersRepository,\n\t\tProductsRepository,\n\t\tSettingsRepository,\n\t]);\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dealpos-npm-ng-dealpos-library-dexie-data-access.mjs","sources":["../../../projects/dealpos-npm/ng-dealpos-library/dexie-data-access/src/db/pos-database.base.ts","../../../projects/dealpos-npm/ng-dealpos-library/dexie-data-access/src/di/pos-database.token.ts","../../../projects/dealpos-npm/ng-dealpos-library/dexie-data-access/src/repositories/order-queue.repository.ts","../../../projects/dealpos-npm/ng-dealpos-library/dexie-data-access/src/repositories/orders.repository.ts","../../../projects/dealpos-npm/ng-dealpos-library/dexie-data-access/src/search/fuzzy-search.ts","../../../projects/dealpos-npm/ng-dealpos-library/dexie-data-access/src/repositories/keyed-table.repository.ts","../../../projects/dealpos-npm/ng-dealpos-library/dexie-data-access/src/repositories/products.repository.ts","../../../projects/dealpos-npm/ng-dealpos-library/dexie-data-access/src/repositories/key-value.repository.ts","../../../projects/dealpos-npm/ng-dealpos-library/dexie-data-access/src/repositories/settings.repository.ts","../../../projects/dealpos-npm/ng-dealpos-library/dexie-data-access/src/di/provide-pos-dexie-data-access.ts","../../../projects/dealpos-npm/ng-dealpos-library/dexie-data-access/dealpos-npm-ng-dealpos-library-dexie-data-access.ts"],"sourcesContent":["import Dexie, { type EntityTable, type IndexableType } from 'dexie';\nimport type {\n\tSalesOrderDataModel,\n\tVariantByIDHttpResponse,\n} from '@dealpos-npm/ng-dealpos-library/sell';\nimport type { OrderRecord, SettingRecord } from './records.model';\n\nexport abstract class PosDatabaseBase<\n\tTProduct extends { ID: IndexableType } = VariantByIDHttpResponse,\n\tTOrder = SalesOrderDataModel,\n> extends Dexie {\n\tproducts!: EntityTable<TProduct, 'ID'>;\n\torders!: EntityTable<OrderRecord<TOrder>, 'id', OrderRecord<TOrder>>;\n\tsettings!: EntityTable<SettingRecord, 'key'>;\n\n\tprotected constructor(name: string) {\n\t\tsuper(name);\n\t\tthis.version(1).stores({\n\t\t\tproducts: 'ID, Code',\n\t\t\torders: '++id, status',\n\t\t\tsettings: 'key',\n\t\t});\n\n\t\t// v2 replaces the auto-increment `orders` primary key with a UUID string.\n\t\t// The schema change already drops the object store, so this explicit clear\n\t\t// only states the intent: old numeric-key order rows are not migrated.\n\t\tthis.version(2)\n\t\t\t.stores({\n\t\t\t\torders: 'id, status',\n\t\t\t})\n\t\t\t.upgrade((tx) => tx.table('orders').clear());\n\t}\n}\n","import { InjectionToken } from '@angular/core';\nimport type { PosDatabaseBase } from '../db/pos-database.base';\n\nexport const POS_DATABASE = new InjectionToken<PosDatabaseBase>('POS_DATABASE');\n","import { liveQuery, type EntityTable } from 'dexie';\nimport { Observable } from 'rxjs';\nimport { v4 as uuidv4 } from 'uuid';\nimport type { SalesOrderDataModel } from '@dealpos-npm/ng-dealpos-library/sell';\nimport type { OrderRecord, OrderStatus } from '../db/records.model';\n\n/** Orders table keyed by a UUID string primary key (`id`). */\nexport type OrderTable<TOrder> = EntityTable<\n\tOrderRecord<TOrder>,\n\t'id',\n\tOrderRecord<TOrder>\n>;\n\nexport type StoredEntity<T> = T & { id?: string };\n\nexport class OrderQueueRepository<TOrder = SalesOrderDataModel> {\n\tconstructor(\n\t\tprotected readonly table: OrderTable<TOrder>,\n\t\tprotected readonly options: {\n\t\t\torderIdExtractor: (order: TOrder) => string | number;\n\t\t},\n\t) {}\n\n\tprivate toPayload(order: TOrder): TOrder {\n\t\tconst payload = JSON.parse(JSON.stringify(order)) as TOrder & { id?: string };\n\t\tdelete payload.id;\n\t\treturn payload as TOrder;\n\t}\n\n\tprivate toStored(record: OrderRecord<TOrder>): StoredEntity<TOrder> {\n\t\treturn Object.assign(record.order as object, { id: record.id }) as StoredEntity<TOrder>;\n\t}\n\n\t/**\n\t * Queues an order. Pass `id` to make the save idempotent — re-saving the same\n\t * order overwrites the existing record instead of creating a duplicate.\n\t * When omitted, a fresh UUID is generated.\n\t */\n\tsaveUnsent(order: TOrder, id?: string): Promise<string> {\n\t\treturn this.table.add({\n\t\t\tid: id ?? uuidv4(),\n\t\t\tstatus: 'unsent',\n\t\t\torder: this.toPayload(order),\n\t\t}) as Promise<string>;\n\t}\n\n\tobserveByStatus(status: OrderStatus): Observable<Array<StoredEntity<TOrder>>> {\n\t\treturn new Observable((subscriber) => {\n\t\t\tconst live = liveQuery(() => this.table.where('status').equals(status).toArray());\n\t\t\tconst subscription = live.subscribe({\n\t\t\t\tnext: (records) => subscriber.next(records.map((record) => this.toStored(record))),\n\t\t\t\terror: (error) => subscriber.error(error),\n\t\t\t});\n\t\t\treturn () => subscription.unsubscribe();\n\t\t});\n\t}\n\n\tasync listByStatus(status: OrderStatus): Promise<Array<StoredEntity<TOrder>>> {\n\t\tconst records = await this.table.where('status').equals(status).toArray();\n\t\treturn records.map((record) => this.toStored(record));\n\t}\n\n\tlistUnsent(): Promise<Array<StoredEntity<TOrder>>> {\n\t\treturn this.listByStatus('unsent');\n\t}\n\n\tlistFailed(): Promise<Array<StoredEntity<TOrder>>> {\n\t\treturn this.listByStatus('failed');\n\t}\n\n\tlistSuccess(): Promise<Array<StoredEntity<TOrder>>> {\n\t\treturn this.listByStatus('success');\n\t}\n\n\tasync findUnsentByOrderId(\n\t\torderId: string | number,\n\t): Promise<StoredEntity<TOrder> | undefined> {\n\t\tconst records = await this.table.where('status').equals('unsent').toArray();\n\t\tconst match = records.find(\n\t\t\t(record) => this.options.orderIdExtractor(record.order) == orderId,\n\t\t);\n\t\treturn match ? this.toStored(match) : undefined;\n\t}\n\n\tasync remove(id: string): Promise<void> {\n\t\tawait this.table.delete(id);\n\t}\n\n\tmarkFailed(id: string, order: TOrder): Promise<string> {\n\t\treturn this.table.put({\n\t\t\tid,\n\t\t\tstatus: 'failed',\n\t\t\torder: this.toPayload(order),\n\t\t}) as Promise<string>;\n\t}\n\n\tmarkSuccess(id: string, order: TOrder): Promise<string> {\n\t\treturn this.table.put({\n\t\t\tid,\n\t\t\tstatus: 'success',\n\t\t\torder: this.toPayload(order),\n\t\t}) as Promise<string>;\n\t}\n\n\tclearFailed(): Promise<number> {\n\t\treturn this.table.where('status').equals('failed').delete();\n\t}\n\n\tclearSuccess(): Promise<number> {\n\t\treturn this.table.where('status').equals('success').delete();\n\t}\n}\n","import { Inject, Injectable } from '@angular/core';\nimport type { SalesOrderDataModel } from '@dealpos-npm/ng-dealpos-library/sell';\nimport { OrderQueueRepository } from './order-queue.repository';\nimport { POS_DATABASE } from '../di/pos-database.token';\nimport type { PosDatabaseBase } from '../db/pos-database.base';\n\nexport type StoredOrder = SalesOrderDataModel & { id?: string };\n\n@Injectable({ providedIn: 'root' })\nexport class OrdersRepository extends OrderQueueRepository<SalesOrderDataModel> {\n\tconstructor(@Inject(POS_DATABASE) db: PosDatabaseBase) {\n\t\tsuper(db.orders, { orderIdExtractor: (o) => o.Order?.ID });\n\t}\n}\n","import Fuse from 'fuse.js';\nimport type {\n\tFuseOptionKey,\n\tFuseResultMatch,\n\tFuseSearchOptions,\n\tFuseSortFunction,\n\tFuseSortFunctionArg,\n\tIFuseOptions,\n} from 'fuse.js';\n\nexport interface FuzzyMatch<T> {\n\titem: T;\n\tscore: number;\n\tmatches: readonly FuseResultMatch[];\n}\n\nconst UNRANKED = Number.MAX_SAFE_INTEGER;\n\nfunction escapeContainsToken(token: string): string {\n\treturn token.replace(/\"/g, '').replace(/\\|/g, '\\\\|');\n}\n\n/**\n * Builds an extended-search pattern that matches by **substring** (\"contains\")\n * instead of the default fuzzy bitap algorithm. Each word becomes one `include`\n * matcher (`'word`) and words are AND-ed because extended search treats\n * space-separated tokens as a conjunction.\n */\nexport function toContainsPattern(query: string): string {\n\treturn query\n\t\t.trim()\n\t\t.split(/\\s+/)\n\t\t.map(escapeContainsToken)\n\t\t.filter((token) => token.length > 0)\n\t\t.map((token) => `'${token}`)\n\t\t.join(' ');\n}\n\nfunction firstMatchIndex(arg: FuseSortFunctionArg): number {\n\tlet first = UNRANKED;\n\tfor (const match of arg.matches ?? []) {\n\t\tconst start = match.indices?.[0]?.[0];\n\t\tif (typeof start === 'number' && start < first) {\n\t\t\tfirst = start;\n\t\t}\n\t}\n\treturn first;\n}\n\nfunction lastMatchIndex(arg: FuseSortFunctionArg): number {\n\tlet last = -1;\n\tfor (const match of arg.matches ?? []) {\n\t\tfor (const range of match.indices ?? []) {\n\t\t\tif (range[0] > last) {\n\t\t\t\tlast = range[0];\n\t\t\t}\n\t\t}\n\t}\n\treturn last;\n}\n\nfunction shortestMatchedValue(arg: FuseSortFunctionArg): number {\n\tlet shortest = UNRANKED;\n\tfor (const match of arg.matches ?? []) {\n\t\tshortest = Math.min(shortest, match.value?.length ?? 0);\n\t}\n\treturn shortest;\n}\n\n/**\n * Substring matches all score 0 in extended search, so relevance is restored\n * here: earliest hit first (prefix beats mid-word), then tightest span, then\n * shortest matched field, then catalog order as the stable tie-break.\n */\nexport const matchPositionSortFn: FuseSortFunction = (a, b) => {\n\tconst first = firstMatchIndex(a) - firstMatchIndex(b);\n\tif (first !== 0) {\n\t\treturn first;\n\t}\n\tconst last = lastMatchIndex(a) - lastMatchIndex(b);\n\tif (last !== 0) {\n\t\treturn last;\n\t}\n\tconst length = shortestMatchedValue(a) - shortestMatchedValue(b);\n\tif (length !== 0) {\n\t\treturn length;\n\t}\n\treturn a.idx - b.idx;\n};\n\nexport function createFuzzyIndex<T>(\n\titems: ReadonlyArray<T>,\n\tkeys: ReadonlyArray<FuseOptionKey<T>>,\n\toptions?: IFuseOptions<T>,\n): Fuse<T> {\n\treturn new Fuse([...items], {\n\t\tincludeScore: true,\n\t\tincludeMatches: true,\n\t\tfindAllMatches: true,\n\t\tuseExtendedSearch: true,\n\t\tsortFn: matchPositionSortFn,\n\t\tkeys: [...keys],\n\t\t...(options ?? {}),\n\t});\n}\n\nexport function fuzzySearch<T>(\n\tindex: Fuse<T>,\n\tquery: string,\n\topts?: FuseSearchOptions,\n): Array<FuzzyMatch<T>> {\n\tconst pattern =\n\t\tindex.options.useExtendedSearch !== false ? toContainsPattern(query) : query.trim();\n\tif (!pattern) {\n\t\treturn [];\n\t}\n\treturn index\n\t\t.search(pattern, opts)\n\t\t.map((r) => ({\n\t\t\titem: r.item,\n\t\t\tscore: r.score ?? 0,\n\t\t\tmatches: r.matches ?? [],\n\t\t}));\n}\n","import type { EntityTable, IDType } from 'dexie';\nimport type { default as Fuse, FuseOptionKey, FuseSearchOptions, IFuseOptions } from 'fuse.js';\nimport { createFuzzyIndex, fuzzySearch } from '../search/fuzzy-search';\nimport type { FuzzyMatch } from '../search/fuzzy-search';\n\nexport class KeyedTableRepository<T, K extends keyof T & string> {\n\tprivate fuzzyIndex: Fuse<T> | undefined;\n\n\tconstructor(\n\t\tprotected readonly table: EntityTable<T, K>,\n\t\tprotected readonly options?: {\n\t\t\tcodeIndex?: string;\n\t\t\tfuzzyKeys?: ReadonlyArray<FuseOptionKey<T>>;\n\t\t\tfuzzyOptions?: IFuseOptions<T>;\n\t\t},\n\t) {}\n\n\tgetAll(): Promise<T[]> {\n\t\treturn this.table.toArray();\n\t}\n\n\tgetById(id: T[K]): Promise<T | undefined> {\n\t\treturn this.table.get(id as IDType<T, K>);\n\t}\n\n\tgetByIndex(index: string, value: unknown): Promise<T | undefined> {\n\t\treturn this.table.where(index).equals(value as never).first();\n\t}\n\n\tasync searchFuzzy(\n\t\tquery: string,\n\t\topts?: FuseSearchOptions,\n\t): Promise<Array<FuzzyMatch<T>>> {\n\t\tconst keys = this.options?.fuzzyKeys;\n\t\tif (!keys || keys.length === 0 || !query) {\n\t\t\treturn [];\n\t\t}\n\t\tif (!this.fuzzyIndex) {\n\t\t\tthis.fuzzyIndex = createFuzzyIndex(\n\t\t\t\tawait this.getAll(),\n\t\t\t\tkeys,\n\t\t\t\tthis.options?.fuzzyOptions,\n\t\t\t);\n\t\t}\n\t\treturn fuzzySearch(this.fuzzyIndex, query, opts);\n\t}\n\n\treplaceAll(items: T[]): Promise<void> {\n\t\tthis.fuzzyIndex = undefined;\n\t\treturn this.table.db.transaction('rw', this.table, async () => {\n\t\t\tawait this.table.clear();\n\t\t\tif (items.length > 0) {\n\t\t\t\tawait this.table.bulkAdd(items);\n\t\t\t}\n\t\t});\n\t}\n\n\tclear(): Promise<void> {\n\t\tthis.fuzzyIndex = undefined;\n\t\treturn this.table.clear();\n\t}\n\n\tcount(): Promise<number> {\n\t\treturn this.table.count();\n\t}\n\n\t/**\n\t * Estimasi ukuran penyimpanan (byte) untuk seluruh baris di tabel ini.\n\t * Dihitung dari panjang serialisasi JSON (UTF-8) tiap record, jadi ini\n\t * hanya perkiraan — belum termasuk overhead structured-clone dan index Dexie.\n\t * Membaca per baris via `table.each()` agar tidak menahan seluruh tabel di memori.\n\t */\n\tasync estimateSize(): Promise<number> {\n\t\tconst encoder = new TextEncoder();\n\t\tlet total = 0;\n\t\tawait this.table.each((item) => {\n\t\t\ttotal += encoder.encode(JSON.stringify(item)).length;\n\t\t});\n\t\treturn total;\n\t}\n}\n","import { Inject, Injectable } from '@angular/core';\nimport type { VariantByIDHttpResponse } from '@dealpos-npm/ng-dealpos-library/sell';\nimport { KeyedTableRepository } from './keyed-table.repository';\nimport { POS_DATABASE } from '../di/pos-database.token';\nimport type { PosDatabaseBase } from '../db/pos-database.base';\n\n@Injectable({ providedIn: 'root' })\nexport class ProductsRepository extends KeyedTableRepository<VariantByIDHttpResponse, 'ID'> {\n\tconstructor(@Inject(POS_DATABASE) db: PosDatabaseBase) {\n\t\tsuper(db.products, { codeIndex: 'Code', fuzzyKeys: ['Name', 'Code'] });\n\t}\n}\n","import type { EntityTable } from 'dexie';\nimport type { SettingKey, SettingRecord } from '../db/records.model';\n\nexport class KeyValueRepository<TMap extends object> {\n\tconstructor(protected readonly table: EntityTable<SettingRecord, 'key'>) {}\n\n\tasync get<K extends keyof TMap & string>(key: K): Promise<TMap[K] | undefined> {\n\t\tconst record = await this.table.get(key as SettingKey);\n\t\treturn record?.value as TMap[K] | undefined;\n\t}\n\n\tput<K extends keyof TMap & string>(key: K, value: TMap[K]): Promise<SettingKey> {\n\t\treturn this.table.put({ key: key as SettingKey, value });\n\t}\n}\n","import { Inject, Injectable } from '@angular/core';\nimport { KeyValueRepository } from './key-value.repository';\nimport { POS_DATABASE } from '../di/pos-database.token';\nimport type { PosDatabaseBase } from '../db/pos-database.base';\nimport type { SettingKey, SyncTimeValue } from '../db/records.model';\n\nexport interface CanonicalSettingsMap {\n\tsyncTime: SyncTimeValue;\n\tLoadPOSAPI: unknown;\n\tactiveRegister: unknown;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class SettingsRepository extends KeyValueRepository<CanonicalSettingsMap> {\n\tconstructor(@Inject(POS_DATABASE) db: PosDatabaseBase) {\n\t\tsuper(db.settings);\n\t}\n\n\tgetSyncTime(): Promise<SyncTimeValue | undefined> {\n\t\treturn this.get('syncTime');\n\t}\n\n\t/**\n\t * Catat waktu sync terakhir, opsional beserta durasi proses sync (ms).\n\t * `durationMs` bersifat opsional agar pemanggil lama tetap kompatibel.\n\t * Saat tidak diisi, field tidak disimpan sehingga record tetap `{ time }`.\n\t */\n\tputSyncTime(time: string, durationMs?: number): Promise<SettingKey> {\n\t\tconst value: SyncTimeValue =\n\t\t\tdurationMs === undefined ? { time } : { time, durationMs };\n\t\treturn this.put('syncTime', value);\n\t}\n\n\tgetLoadPOSAPI<T = unknown>(): Promise<T | undefined> {\n\t\treturn this.get('LoadPOSAPI') as Promise<T | undefined>;\n\t}\n\n\tputLoadPOSAPI<T>(value: T): Promise<SettingKey> {\n\t\treturn this.put('LoadPOSAPI', value);\n\t}\n\n\tasync getActiveRegister<T = unknown>(): Promise<T | null> {\n\t\treturn ((await this.get('activeRegister')) as T | undefined) ?? null;\n\t}\n\n\tputActiveRegister<T>(value: T): Promise<SettingKey> {\n\t\treturn this.put('activeRegister', value);\n\t}\n}\n","import { makeEnvironmentProviders } from '@angular/core';\nimport type { EnvironmentProviders, Type } from '@angular/core';\nimport { POS_DATABASE } from './pos-database.token';\nimport type { PosDatabaseBase } from '../db/pos-database.base';\nimport { OrdersRepository } from '../repositories/orders.repository';\nimport { ProductsRepository } from '../repositories/products.repository';\nimport { SettingsRepository } from '../repositories/settings.repository';\n\nexport function provideDexieDataAccess(database: Type<PosDatabaseBase>): EnvironmentProviders {\n\treturn makeEnvironmentProviders([\n\t\tdatabase,\n\t\t{ provide: POS_DATABASE, useExisting: database },\n\t\tOrdersRepository,\n\t\tProductsRepository,\n\t\tSettingsRepository,\n\t]);\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":["uuidv4"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dealpos-npm/ng-dealpos-library",
|
|
3
|
-
"version": "21.0.
|
|
3
|
+
"version": "21.0.41",
|
|
4
4
|
"peerDependencies": {
|
|
5
5
|
"@angular/common": "^21.0.5",
|
|
6
6
|
"@angular/core": "^21.0.5",
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
"primeng": "^21.2.0",
|
|
30
30
|
"primeicons": "^7.0.0",
|
|
31
31
|
"papaparse": "^5.5.3",
|
|
32
|
-
"simple-keyboard": "^3.8.93"
|
|
32
|
+
"simple-keyboard": "^3.8.93",
|
|
33
|
+
"uuid": "^14.0.2"
|
|
33
34
|
},
|
|
34
35
|
"dependencies": {
|
|
35
36
|
"tslib": "^2.3.0"
|
|
@@ -121,6 +122,10 @@
|
|
|
121
122
|
"types": "./types/dealpos-npm-ng-dealpos-library-components-modal-material.d.ts",
|
|
122
123
|
"default": "./fesm2022/dealpos-npm-ng-dealpos-library-components-modal-material.mjs"
|
|
123
124
|
},
|
|
125
|
+
"./components/modal-news": {
|
|
126
|
+
"types": "./types/dealpos-npm-ng-dealpos-library-components-modal-news.d.ts",
|
|
127
|
+
"default": "./fesm2022/dealpos-npm-ng-dealpos-library-components-modal-news.mjs"
|
|
128
|
+
},
|
|
124
129
|
"./components/news-swiper": {
|
|
125
130
|
"types": "./types/dealpos-npm-ng-dealpos-library-components-news-swiper.d.ts",
|
|
126
131
|
"default": "./fesm2022/dealpos-npm-ng-dealpos-library-components-news-swiper.mjs"
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { MatDialogRef } from '@angular/material/dialog';
|
|
3
|
+
|
|
4
|
+
interface NewsModalItem {
|
|
5
|
+
id: string;
|
|
6
|
+
title: string;
|
|
7
|
+
body: string;
|
|
8
|
+
image?: string;
|
|
9
|
+
imageAlt?: string;
|
|
10
|
+
dismissLabel?: string;
|
|
11
|
+
actionLabel?: string;
|
|
12
|
+
actionUrl?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Route paths where this item may auto-open. Empty or undefined means the item is
|
|
15
|
+
* eligible on any route. Matching is a case-insensitive, per-segment prefix match, so
|
|
16
|
+
* `/menu/Products` matches `/menu/Products/Detail` but not `/menu/ProductsFoo`.
|
|
17
|
+
*/
|
|
18
|
+
targets?: string[];
|
|
19
|
+
}
|
|
20
|
+
interface NewsModalLabels {
|
|
21
|
+
close?: string;
|
|
22
|
+
prev?: string;
|
|
23
|
+
next?: string;
|
|
24
|
+
gotIt?: string;
|
|
25
|
+
goToNews?: string;
|
|
26
|
+
dialogLabel?: string;
|
|
27
|
+
}
|
|
28
|
+
interface NewsModalData {
|
|
29
|
+
items: NewsModalItem[];
|
|
30
|
+
startIndex?: number;
|
|
31
|
+
labels?: NewsModalLabels;
|
|
32
|
+
}
|
|
33
|
+
interface NewsModalResult {
|
|
34
|
+
action: 'dismiss' | 'closed' | 'action';
|
|
35
|
+
index: number;
|
|
36
|
+
item: NewsModalItem;
|
|
37
|
+
url?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
declare class ModalNewsComponent {
|
|
41
|
+
readonly data: NewsModalData;
|
|
42
|
+
private readonly dialogRef;
|
|
43
|
+
readonly labels: i0.Signal<Required<NewsModalLabels>>;
|
|
44
|
+
readonly index: i0.WritableSignal<number>;
|
|
45
|
+
readonly total: i0.Signal<number>;
|
|
46
|
+
readonly isMulti: i0.Signal<boolean>;
|
|
47
|
+
readonly item: i0.Signal<NewsModalItem | null>;
|
|
48
|
+
private touchStartX;
|
|
49
|
+
readonly titleId = "lib-modal-news-title";
|
|
50
|
+
next(): void;
|
|
51
|
+
prev(): void;
|
|
52
|
+
goTo(i: number): void;
|
|
53
|
+
dismiss(): void;
|
|
54
|
+
close(): void;
|
|
55
|
+
onAction(): void;
|
|
56
|
+
onTouchStart(event: TouchEvent): void;
|
|
57
|
+
onTouchEnd(event: TouchEvent): void;
|
|
58
|
+
onKeydown(event: KeyboardEvent): void;
|
|
59
|
+
private move;
|
|
60
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<ModalNewsComponent, never>;
|
|
61
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<ModalNewsComponent, "lib-modal-news", never, {}, {}, never, never, true, never>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface NewsModalOpenOptions {
|
|
65
|
+
startIndex?: number;
|
|
66
|
+
labels?: NewsModalLabels;
|
|
67
|
+
}
|
|
68
|
+
declare class NewsModalService {
|
|
69
|
+
private readonly dialog;
|
|
70
|
+
open(items: NewsModalItem[], options?: NewsModalOpenOptions): MatDialogRef<ModalNewsComponent, NewsModalResult | undefined>;
|
|
71
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<NewsModalService, never>;
|
|
72
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<NewsModalService>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface NewsModalAutoOpenOptions {
|
|
76
|
+
startIndex?: number;
|
|
77
|
+
labels?: NewsModalLabels;
|
|
78
|
+
onOpen?: (items: NewsModalItem[]) => void;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Opens the news modal automatically when the current route matches the `targets` of any
|
|
82
|
+
* supplied item. Persisting "already seen" state is deliberately NOT handled here — the host
|
|
83
|
+
* owns that, and can pass a getter that returns only items the user has not seen yet.
|
|
84
|
+
*/
|
|
85
|
+
declare class NewsModalRouterService {
|
|
86
|
+
private readonly router;
|
|
87
|
+
private readonly newsModal;
|
|
88
|
+
private readonly dialogOpen;
|
|
89
|
+
/**
|
|
90
|
+
* Evaluates the items against the current URL immediately and again on every navigation.
|
|
91
|
+
* Returns a teardown function that stops watching; it does not close an open dialog.
|
|
92
|
+
*/
|
|
93
|
+
autoOpen(source: NewsModalItem[] | (() => NewsModalItem[]), options?: NewsModalAutoOpenOptions): () => void;
|
|
94
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<NewsModalRouterService, never>;
|
|
95
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<NewsModalRouterService>;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export { ModalNewsComponent, NewsModalRouterService, NewsModalService };
|
|
99
|
+
export type { NewsModalAutoOpenOptions, NewsModalData, NewsModalItem, NewsModalLabels, NewsModalOpenOptions, NewsModalResult };
|
|
@@ -3,12 +3,12 @@ import { SalesOrderDataModel, VariantByIDHttpResponse } from '@dealpos-npm/ng-de
|
|
|
3
3
|
import Dexie, { IndexableType, EntityTable } from 'dexie';
|
|
4
4
|
import * as i0 from '@angular/core';
|
|
5
5
|
import { InjectionToken, Type, EnvironmentProviders } from '@angular/core';
|
|
6
|
-
import Fuse, { FuseResultMatch, FuseOptionKey, IFuseOptions, FuseSearchOptions } from 'fuse.js';
|
|
6
|
+
import Fuse, { FuseResultMatch, FuseSortFunction, FuseOptionKey, IFuseOptions, FuseSearchOptions } from 'fuse.js';
|
|
7
7
|
import { Observable } from 'rxjs';
|
|
8
8
|
|
|
9
9
|
type OrderStatus = 'unsent' | 'failed' | 'success';
|
|
10
10
|
interface OrderRecord<TOrder = SalesOrderDataModel> {
|
|
11
|
-
id
|
|
11
|
+
id: string;
|
|
12
12
|
status: OrderStatus;
|
|
13
13
|
order: TOrder;
|
|
14
14
|
}
|
|
@@ -19,13 +19,19 @@ interface SettingRecord {
|
|
|
19
19
|
}
|
|
20
20
|
interface SyncTimeValue {
|
|
21
21
|
time: string;
|
|
22
|
+
/**
|
|
23
|
+
* Durasi proses sync terakhir dalam milidetik.
|
|
24
|
+
* Opsional: record lama (dan pemanggil yang tidak mengukur durasi)
|
|
25
|
+
* tetap valid tanpa field ini.
|
|
26
|
+
*/
|
|
27
|
+
durationMs?: number;
|
|
22
28
|
}
|
|
23
29
|
|
|
24
30
|
declare abstract class PosDatabaseBase<TProduct extends {
|
|
25
31
|
ID: IndexableType;
|
|
26
32
|
} = VariantByIDHttpResponse, TOrder = SalesOrderDataModel> extends Dexie {
|
|
27
33
|
products: EntityTable<TProduct, 'ID'>;
|
|
28
|
-
orders: EntityTable<OrderRecord<TOrder>, 'id'
|
|
34
|
+
orders: EntityTable<OrderRecord<TOrder>, 'id', OrderRecord<TOrder>>;
|
|
29
35
|
settings: EntityTable<SettingRecord, 'key'>;
|
|
30
36
|
protected constructor(name: string);
|
|
31
37
|
}
|
|
@@ -39,6 +45,19 @@ interface FuzzyMatch<T> {
|
|
|
39
45
|
score: number;
|
|
40
46
|
matches: readonly FuseResultMatch[];
|
|
41
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Builds an extended-search pattern that matches by **substring** ("contains")
|
|
50
|
+
* instead of the default fuzzy bitap algorithm. Each word becomes one `include`
|
|
51
|
+
* matcher (`'word`) and words are AND-ed because extended search treats
|
|
52
|
+
* space-separated tokens as a conjunction.
|
|
53
|
+
*/
|
|
54
|
+
declare function toContainsPattern(query: string): string;
|
|
55
|
+
/**
|
|
56
|
+
* Substring matches all score 0 in extended search, so relevance is restored
|
|
57
|
+
* here: earliest hit first (prefix beats mid-word), then tightest span, then
|
|
58
|
+
* shortest matched field, then catalog order as the stable tie-break.
|
|
59
|
+
*/
|
|
60
|
+
declare const matchPositionSortFn: FuseSortFunction;
|
|
42
61
|
declare function createFuzzyIndex<T>(items: ReadonlyArray<T>, keys: ReadonlyArray<FuseOptionKey<T>>, options?: IFuseOptions<T>): Fuse<T>;
|
|
43
62
|
declare function fuzzySearch<T>(index: Fuse<T>, query: string, opts?: FuseSearchOptions): Array<FuzzyMatch<T>>;
|
|
44
63
|
|
|
@@ -62,31 +81,45 @@ declare class KeyedTableRepository<T, K extends keyof T & string> {
|
|
|
62
81
|
replaceAll(items: T[]): Promise<void>;
|
|
63
82
|
clear(): Promise<void>;
|
|
64
83
|
count(): Promise<number>;
|
|
84
|
+
/**
|
|
85
|
+
* Estimasi ukuran penyimpanan (byte) untuk seluruh baris di tabel ini.
|
|
86
|
+
* Dihitung dari panjang serialisasi JSON (UTF-8) tiap record, jadi ini
|
|
87
|
+
* hanya perkiraan — belum termasuk overhead structured-clone dan index Dexie.
|
|
88
|
+
* Membaca per baris via `table.each()` agar tidak menahan seluruh tabel di memori.
|
|
89
|
+
*/
|
|
90
|
+
estimateSize(): Promise<number>;
|
|
65
91
|
}
|
|
66
92
|
|
|
93
|
+
/** Orders table keyed by a UUID string primary key (`id`). */
|
|
94
|
+
type OrderTable<TOrder> = EntityTable<OrderRecord<TOrder>, 'id', OrderRecord<TOrder>>;
|
|
67
95
|
type StoredEntity<T> = T & {
|
|
68
|
-
id?:
|
|
96
|
+
id?: string;
|
|
69
97
|
};
|
|
70
98
|
declare class OrderQueueRepository<TOrder = SalesOrderDataModel> {
|
|
71
|
-
protected readonly table:
|
|
99
|
+
protected readonly table: OrderTable<TOrder>;
|
|
72
100
|
protected readonly options: {
|
|
73
101
|
orderIdExtractor: (order: TOrder) => string | number;
|
|
74
102
|
};
|
|
75
|
-
constructor(table:
|
|
103
|
+
constructor(table: OrderTable<TOrder>, options: {
|
|
76
104
|
orderIdExtractor: (order: TOrder) => string | number;
|
|
77
105
|
});
|
|
78
106
|
private toPayload;
|
|
79
107
|
private toStored;
|
|
80
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Queues an order. Pass `id` to make the save idempotent — re-saving the same
|
|
110
|
+
* order overwrites the existing record instead of creating a duplicate.
|
|
111
|
+
* When omitted, a fresh UUID is generated.
|
|
112
|
+
*/
|
|
113
|
+
saveUnsent(order: TOrder, id?: string): Promise<string>;
|
|
81
114
|
observeByStatus(status: OrderStatus): Observable<Array<StoredEntity<TOrder>>>;
|
|
82
115
|
listByStatus(status: OrderStatus): Promise<Array<StoredEntity<TOrder>>>;
|
|
83
116
|
listUnsent(): Promise<Array<StoredEntity<TOrder>>>;
|
|
84
117
|
listFailed(): Promise<Array<StoredEntity<TOrder>>>;
|
|
85
118
|
listSuccess(): Promise<Array<StoredEntity<TOrder>>>;
|
|
86
119
|
findUnsentByOrderId(orderId: string | number): Promise<StoredEntity<TOrder> | undefined>;
|
|
87
|
-
remove(id:
|
|
88
|
-
markFailed(id:
|
|
89
|
-
markSuccess(id:
|
|
120
|
+
remove(id: string): Promise<void>;
|
|
121
|
+
markFailed(id: string, order: TOrder): Promise<string>;
|
|
122
|
+
markSuccess(id: string, order: TOrder): Promise<string>;
|
|
90
123
|
clearFailed(): Promise<number>;
|
|
91
124
|
clearSuccess(): Promise<number>;
|
|
92
125
|
}
|
|
@@ -99,7 +132,7 @@ declare class KeyValueRepository<TMap extends object> {
|
|
|
99
132
|
}
|
|
100
133
|
|
|
101
134
|
type StoredOrder = SalesOrderDataModel & {
|
|
102
|
-
id?:
|
|
135
|
+
id?: string;
|
|
103
136
|
};
|
|
104
137
|
declare class OrdersRepository extends OrderQueueRepository<SalesOrderDataModel> {
|
|
105
138
|
constructor(db: PosDatabaseBase);
|
|
@@ -121,7 +154,12 @@ interface CanonicalSettingsMap {
|
|
|
121
154
|
declare class SettingsRepository extends KeyValueRepository<CanonicalSettingsMap> {
|
|
122
155
|
constructor(db: PosDatabaseBase);
|
|
123
156
|
getSyncTime(): Promise<SyncTimeValue | undefined>;
|
|
124
|
-
|
|
157
|
+
/**
|
|
158
|
+
* Catat waktu sync terakhir, opsional beserta durasi proses sync (ms).
|
|
159
|
+
* `durationMs` bersifat opsional agar pemanggil lama tetap kompatibel.
|
|
160
|
+
* Saat tidak diisi, field tidak disimpan sehingga record tetap `{ time }`.
|
|
161
|
+
*/
|
|
162
|
+
putSyncTime(time: string, durationMs?: number): Promise<SettingKey>;
|
|
125
163
|
getLoadPOSAPI<T = unknown>(): Promise<T | undefined>;
|
|
126
164
|
putLoadPOSAPI<T>(value: T): Promise<SettingKey>;
|
|
127
165
|
getActiveRegister<T = unknown>(): Promise<T | null>;
|
|
@@ -130,5 +168,5 @@ declare class SettingsRepository extends KeyValueRepository<CanonicalSettingsMap
|
|
|
130
168
|
static ɵprov: i0.ɵɵInjectableDeclaration<SettingsRepository>;
|
|
131
169
|
}
|
|
132
170
|
|
|
133
|
-
export { KeyValueRepository, KeyedTableRepository, OrderQueueRepository, OrdersRepository, POS_DATABASE, PosDatabaseBase, ProductsRepository, SettingsRepository, createFuzzyIndex, fuzzySearch, provideDexieDataAccess };
|
|
134
|
-
export type { CanonicalSettingsMap, FuzzyMatch, OrderRecord, OrderStatus, SettingKey, SettingRecord, StoredEntity, StoredOrder, SyncTimeValue };
|
|
171
|
+
export { KeyValueRepository, KeyedTableRepository, OrderQueueRepository, OrdersRepository, POS_DATABASE, PosDatabaseBase, ProductsRepository, SettingsRepository, createFuzzyIndex, fuzzySearch, matchPositionSortFn, provideDexieDataAccess, toContainsPattern };
|
|
172
|
+
export type { CanonicalSettingsMap, FuzzyMatch, OrderRecord, OrderStatus, OrderTable, SettingKey, SettingRecord, StoredEntity, StoredOrder, SyncTimeValue };
|