@retalia/pos-components 0.0.4 → 0.0.6
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/README.md +24 -1
- package/fesm2022/retalia-pos-components-services.mjs +171 -0
- package/fesm2022/retalia-pos-components-services.mjs.map +1 -0
- package/fesm2022/retalia-pos-components.mjs +1089 -0
- package/fesm2022/retalia-pos-components.mjs.map +1 -0
- package/package.json +43 -17
- package/types/retalia-pos-components-services.d.ts +168 -0
- package/types/retalia-pos-components.d.ts +300 -0
- package/eslint.config.js +0 -32
- package/ng-package.json +0 -14
- package/src/lib/basket/pos-basket.html +0 -124
- package/src/lib/basket/pos-basket.scss +0 -285
- package/src/lib/basket/pos-basket.spec.ts +0 -198
- package/src/lib/basket/pos-basket.stories.ts +0 -114
- package/src/lib/basket/pos-basket.ts +0 -142
- package/src/lib/item-entry/pos-item-entry.html +0 -41
- package/src/lib/item-entry/pos-item-entry.scss +0 -145
- package/src/lib/item-entry/pos-item-entry.spec.ts +0 -123
- package/src/lib/item-entry/pos-item-entry.stories.ts +0 -31
- package/src/lib/item-entry/pos-item-entry.ts +0 -81
- package/src/lib/login/pos-login.html +0 -82
- package/src/lib/login/pos-login.scss +0 -238
- package/src/lib/login/pos-login.spec.ts +0 -89
- package/src/lib/login/pos-login.stories.ts +0 -36
- package/src/lib/login/pos-login.ts +0 -150
- package/src/lib/receipt/pos-receipt.html +0 -116
- package/src/lib/receipt/pos-receipt.scss +0 -367
- package/src/lib/receipt/pos-receipt.spec.ts +0 -188
- package/src/lib/receipt/pos-receipt.stories.ts +0 -163
- package/src/lib/receipt/pos-receipt.ts +0 -129
- package/src/lib/tender/pos-tender.html +0 -79
- package/src/lib/tender/pos-tender.scss +0 -318
- package/src/lib/tender/pos-tender.spec.ts +0 -150
- package/src/lib/tender/pos-tender.stories.ts +0 -47
- package/src/lib/tender/pos-tender.ts +0 -154
- package/src/lib/tokens/pos-theme-css.spec.ts +0 -75
- package/src/lib/tokens/pos-theme-css.ts +0 -123
- package/src/lib/tokens/pos-token-catalog.ts +0 -514
- package/src/lib/tokens/pos-tokens.html +0 -351
- package/src/lib/tokens/pos-tokens.scss +0 -437
- package/src/lib/tokens/pos-tokens.spec.ts +0 -164
- package/src/lib/tokens/pos-tokens.stories.ts +0 -22
- package/src/lib/tokens/pos-tokens.ts +0 -208
- package/src/public-api.ts +0 -37
- package/tsconfig.lib.json +0 -14
- package/tsconfig.lib.prod.json +0 -11
- package/tsconfig.spec.json +0 -11
- /package/{src/styles → styles}/styles.css +0 -0
- /package/{src/styles → styles}/tokens.css +0 -0
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
:host {
|
|
2
|
-
display: block;
|
|
3
|
-
width: 100%;
|
|
4
|
-
max-width: 26rem;
|
|
5
|
-
color: var(--pos-color-text);
|
|
6
|
-
font-family: var(--pos-font-family);
|
|
7
|
-
font-size: var(--pos-font-size-md);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
.pos-login__panel {
|
|
11
|
-
position: relative;
|
|
12
|
-
display: flex;
|
|
13
|
-
flex-direction: column;
|
|
14
|
-
min-height: 100%;
|
|
15
|
-
padding: var(--pos-space-xl);
|
|
16
|
-
border: 1px solid var(--pos-color-border);
|
|
17
|
-
border-radius: var(--pos-radius-lg);
|
|
18
|
-
background:
|
|
19
|
-
radial-gradient(120% 80% at 100% 0%, rgb(0 194 178 / 0.08), transparent 55%),
|
|
20
|
-
var(--pos-color-panel);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
.pos-login__header {
|
|
24
|
-
position: relative;
|
|
25
|
-
display: flex;
|
|
26
|
-
flex-direction: column;
|
|
27
|
-
align-items: flex-start;
|
|
28
|
-
gap: var(--pos-space-sm);
|
|
29
|
-
padding-right: var(--pos-space-2xl);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
.pos-login__kicker {
|
|
33
|
-
margin: 0;
|
|
34
|
-
color: var(--pos-color-primary);
|
|
35
|
-
font-size: var(--pos-font-size-sm);
|
|
36
|
-
font-weight: var(--pos-font-weight-bold);
|
|
37
|
-
letter-spacing: 0.14em;
|
|
38
|
-
text-transform: uppercase;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
.pos-login__title {
|
|
42
|
-
margin: 0;
|
|
43
|
-
max-width: 14ch;
|
|
44
|
-
font-family: var(--pos-font-family-display);
|
|
45
|
-
font-size: var(--pos-font-size-xl);
|
|
46
|
-
font-weight: var(--pos-font-weight-bold);
|
|
47
|
-
letter-spacing: -0.03em;
|
|
48
|
-
line-height: 1.15;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
.pos-login__status {
|
|
52
|
-
min-height: 1.5rem;
|
|
53
|
-
margin: var(--pos-space-md) 0 var(--pos-space-sm);
|
|
54
|
-
display: flex;
|
|
55
|
-
flex-direction: column;
|
|
56
|
-
gap: var(--pos-space-sm);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
.pos-login__status p {
|
|
60
|
-
margin: 0;
|
|
61
|
-
font-size: var(--pos-font-size-sm);
|
|
62
|
-
font-weight: var(--pos-font-weight-semibold);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
.pos-login__error {
|
|
66
|
-
padding: var(--pos-space-sm) var(--pos-space-md);
|
|
67
|
-
border-left: 3px solid var(--pos-color-danger);
|
|
68
|
-
background: rgb(214 69 69 / 0.08);
|
|
69
|
-
color: var(--pos-color-danger);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
.pos-login__key:disabled,
|
|
73
|
-
.pos-login__back:disabled {
|
|
74
|
-
cursor: not-allowed;
|
|
75
|
-
opacity: 0.55;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
.pos-login__body {
|
|
79
|
-
display: flex;
|
|
80
|
-
flex: 1 1 auto;
|
|
81
|
-
flex-direction: column;
|
|
82
|
-
gap: var(--pos-space-xl);
|
|
83
|
-
width: 100%;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
.pos-login__display {
|
|
87
|
-
border: 1px solid var(--pos-color-border);
|
|
88
|
-
border-radius: var(--pos-radius-lg);
|
|
89
|
-
background: var(--pos-color-display);
|
|
90
|
-
box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.06);
|
|
91
|
-
padding: var(--pos-space-lg) var(--pos-space-xl);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
.pos-login__input {
|
|
95
|
-
display: block;
|
|
96
|
-
width: 100%;
|
|
97
|
-
margin: 0;
|
|
98
|
-
border: 0;
|
|
99
|
-
background: transparent;
|
|
100
|
-
color: var(--pos-color-on-display);
|
|
101
|
-
font-family: var(--pos-font-family-display);
|
|
102
|
-
font-size: clamp(1.35rem, 3vw, 1.85rem);
|
|
103
|
-
font-weight: var(--pos-font-weight-semibold);
|
|
104
|
-
letter-spacing: var(--pos-letter-spacing-display);
|
|
105
|
-
text-align: center;
|
|
106
|
-
outline: none;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
.pos-login__input::placeholder {
|
|
110
|
-
color: rgb(232 237 242 / 0.35);
|
|
111
|
-
letter-spacing: 0.45em;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
.pos-login__keypad {
|
|
115
|
-
display: flex;
|
|
116
|
-
flex-direction: column;
|
|
117
|
-
gap: var(--pos-space-md);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
.pos-login__keypad-row {
|
|
121
|
-
display: grid;
|
|
122
|
-
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
123
|
-
gap: var(--pos-space-md);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
.pos-login__key {
|
|
127
|
-
display: inline-flex;
|
|
128
|
-
align-items: center;
|
|
129
|
-
justify-content: center;
|
|
130
|
-
min-height: var(--pos-keypad-size);
|
|
131
|
-
border: 1px solid var(--pos-color-key-border);
|
|
132
|
-
border-radius: var(--pos-radius-md);
|
|
133
|
-
background: var(--pos-color-key);
|
|
134
|
-
color: var(--pos-color-text);
|
|
135
|
-
font-family: inherit;
|
|
136
|
-
font-size: 1.15rem;
|
|
137
|
-
font-weight: var(--pos-font-weight-bold);
|
|
138
|
-
cursor: pointer;
|
|
139
|
-
transition:
|
|
140
|
-
background 120ms ease,
|
|
141
|
-
color 120ms ease,
|
|
142
|
-
border-color 120ms ease,
|
|
143
|
-
transform 120ms ease;
|
|
144
|
-
-webkit-tap-highlight-color: transparent;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
.pos-login__key--action {
|
|
148
|
-
color: var(--pos-color-text-muted);
|
|
149
|
-
background: var(--pos-color-surface-muted);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
.pos-login__key:active {
|
|
153
|
-
background: var(--pos-color-key-active);
|
|
154
|
-
border-color: var(--pos-color-key-active);
|
|
155
|
-
color: var(--pos-color-on-key-active);
|
|
156
|
-
transform: translateY(1px);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
.pos-login__actions {
|
|
160
|
-
display: flex;
|
|
161
|
-
align-items: stretch;
|
|
162
|
-
gap: var(--pos-space-md);
|
|
163
|
-
margin-top: auto;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
.pos-login__back {
|
|
167
|
-
display: inline-flex;
|
|
168
|
-
flex: 0 0 auto;
|
|
169
|
-
align-items: center;
|
|
170
|
-
gap: var(--pos-space-xs);
|
|
171
|
-
min-height: var(--pos-button-min-height);
|
|
172
|
-
padding: 0 var(--pos-space-lg);
|
|
173
|
-
border: 1px solid var(--pos-color-border);
|
|
174
|
-
border-radius: var(--pos-radius-md);
|
|
175
|
-
background: var(--pos-color-panel-elevated);
|
|
176
|
-
color: var(--pos-color-text);
|
|
177
|
-
font: inherit;
|
|
178
|
-
font-weight: var(--pos-font-weight-semibold);
|
|
179
|
-
cursor: pointer;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
.pos-login__primary {
|
|
183
|
-
flex: 1 1 auto;
|
|
184
|
-
min-height: var(--pos-button-min-height);
|
|
185
|
-
border: 0;
|
|
186
|
-
border-radius: var(--pos-radius-md);
|
|
187
|
-
background: var(--pos-color-primary);
|
|
188
|
-
color: var(--pos-color-on-primary);
|
|
189
|
-
font: inherit;
|
|
190
|
-
font-size: 1.05rem;
|
|
191
|
-
font-weight: var(--pos-font-weight-bold);
|
|
192
|
-
letter-spacing: 0.04em;
|
|
193
|
-
text-transform: uppercase;
|
|
194
|
-
cursor: pointer;
|
|
195
|
-
transition: filter 120ms ease, transform 120ms ease;
|
|
196
|
-
-webkit-tap-highlight-color: transparent;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
.pos-login__primary:not(:disabled):hover {
|
|
200
|
-
filter: brightness(0.97);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
.pos-login__primary:not(:disabled):active {
|
|
204
|
-
transform: translateY(1px);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
.pos-login__primary--disabled,
|
|
208
|
-
.pos-login__primary:disabled {
|
|
209
|
-
background: var(--pos-color-disabled);
|
|
210
|
-
color: var(--pos-color-on-disabled);
|
|
211
|
-
cursor: not-allowed;
|
|
212
|
-
filter: none;
|
|
213
|
-
transform: none;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
@media (min-width: 768px) {
|
|
217
|
-
.pos-login__panel {
|
|
218
|
-
padding: var(--pos-space-2xl);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
.pos-login__key {
|
|
222
|
-
min-height: var(--pos-keypad-size-md);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
@media (min-width: 1100px) {
|
|
227
|
-
.pos-login__key {
|
|
228
|
-
min-height: var(--pos-keypad-size-lg);
|
|
229
|
-
font-size: 1.25rem;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
@media (prefers-reduced-motion: reduce) {
|
|
234
|
-
.pos-login__key,
|
|
235
|
-
.pos-login__primary {
|
|
236
|
-
transition: none;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
-
|
|
3
|
-
import { PosLogin, PosLoginIntent } from './pos-login';
|
|
4
|
-
|
|
5
|
-
describe('PosLogin', () => {
|
|
6
|
-
let fixture: ComponentFixture<PosLogin>;
|
|
7
|
-
let component: PosLogin;
|
|
8
|
-
|
|
9
|
-
beforeEach(async () => {
|
|
10
|
-
await TestBed.configureTestingModule({
|
|
11
|
-
imports: [PosLogin],
|
|
12
|
-
}).compileComponents();
|
|
13
|
-
|
|
14
|
-
fixture = TestBed.createComponent(PosLogin);
|
|
15
|
-
component = fixture.componentInstance;
|
|
16
|
-
await fixture.whenStable();
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('should render the clerk number title by default', () => {
|
|
20
|
-
const compiled = fixture.nativeElement as HTMLElement;
|
|
21
|
-
expect(compiled.querySelector('.pos-login__title')?.textContent).toContain(
|
|
22
|
-
'Enter Clerk Number',
|
|
23
|
-
);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('should emit next with the clerk number after keypad entry', async () => {
|
|
27
|
-
const intents: PosLoginIntent[] = [];
|
|
28
|
-
component.action.subscribe((intent) => intents.push(intent));
|
|
29
|
-
|
|
30
|
-
const keys = Array.from(
|
|
31
|
-
(fixture.nativeElement as HTMLElement).querySelectorAll('.pos-login__key'),
|
|
32
|
-
) as HTMLButtonElement[];
|
|
33
|
-
keys.find((button) => button.textContent?.trim() === '1')?.click();
|
|
34
|
-
keys.find((button) => button.textContent?.trim() === '2')?.click();
|
|
35
|
-
await fixture.whenStable();
|
|
36
|
-
|
|
37
|
-
const primary = (fixture.nativeElement as HTMLElement).querySelector(
|
|
38
|
-
'.pos-login__primary',
|
|
39
|
-
) as HTMLButtonElement;
|
|
40
|
-
primary.click();
|
|
41
|
-
await fixture.whenStable();
|
|
42
|
-
|
|
43
|
-
expect(
|
|
44
|
-
intents.some(
|
|
45
|
-
(intent) => intent.type === 'login.next' && intent.clerkNumber === '12',
|
|
46
|
-
),
|
|
47
|
-
).toBe(true);
|
|
48
|
-
expect(
|
|
49
|
-
(fixture.nativeElement as HTMLElement).querySelector('.pos-login__title')
|
|
50
|
-
?.textContent,
|
|
51
|
-
).toContain('Enter Clerk Password');
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('should display session state from the host without calling a service', async () => {
|
|
55
|
-
fixture.componentRef.setInput('session', {
|
|
56
|
-
status: 'signedIn',
|
|
57
|
-
displayName: 'Alex Rivera',
|
|
58
|
-
});
|
|
59
|
-
await fixture.whenStable();
|
|
60
|
-
|
|
61
|
-
expect(
|
|
62
|
-
(fixture.nativeElement as HTMLElement).querySelector('.pos-login__status')
|
|
63
|
-
?.textContent,
|
|
64
|
-
).toContain('Signed in as Alex Rivera');
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('should not emit submit while session is busy', async () => {
|
|
68
|
-
const intents: PosLoginIntent[] = [];
|
|
69
|
-
component.action.subscribe((intent) => intents.push(intent));
|
|
70
|
-
|
|
71
|
-
fixture.componentRef.setInput('session', { status: 'busy' });
|
|
72
|
-
await fixture.whenStable();
|
|
73
|
-
|
|
74
|
-
const keys = Array.from(
|
|
75
|
-
(fixture.nativeElement as HTMLElement).querySelectorAll('.pos-login__key'),
|
|
76
|
-
) as HTMLButtonElement[];
|
|
77
|
-
keys.find((button) => button.textContent?.trim() === '1')?.click();
|
|
78
|
-
const primary = (fixture.nativeElement as HTMLElement).querySelector(
|
|
79
|
-
'.pos-login__primary',
|
|
80
|
-
) as HTMLButtonElement;
|
|
81
|
-
primary.click();
|
|
82
|
-
await fixture.whenStable();
|
|
83
|
-
|
|
84
|
-
expect(intents.some((intent) => intent.type === 'login.submit')).toBe(
|
|
85
|
-
false,
|
|
86
|
-
);
|
|
87
|
-
expect(primary.disabled).toBe(true);
|
|
88
|
-
});
|
|
89
|
-
});
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type { Meta, StoryObj } from '@storybook/angular-vite';
|
|
2
|
-
|
|
3
|
-
import { PosLogin } from './pos-login';
|
|
4
|
-
|
|
5
|
-
const meta: Meta<PosLogin> = {
|
|
6
|
-
title: 'Login',
|
|
7
|
-
component: PosLogin,
|
|
8
|
-
args: {
|
|
9
|
-
errorMessage: '',
|
|
10
|
-
session: { status: 'signedOut' },
|
|
11
|
-
},
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export default meta;
|
|
15
|
-
|
|
16
|
-
type Story = StoryObj<PosLogin>;
|
|
17
|
-
|
|
18
|
-
export const Default: Story = {};
|
|
19
|
-
|
|
20
|
-
export const Busy: Story = {
|
|
21
|
-
args: {
|
|
22
|
-
session: { status: 'busy' },
|
|
23
|
-
},
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
export const SignedIn: Story = {
|
|
27
|
-
args: {
|
|
28
|
-
session: { status: 'signedIn', displayName: 'Alex Rivera' },
|
|
29
|
-
},
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export const Error: Story = {
|
|
33
|
-
args: {
|
|
34
|
-
errorMessage: 'Invalid clerk or password',
|
|
35
|
-
},
|
|
36
|
-
};
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ChangeDetectionStrategy,
|
|
3
|
-
Component,
|
|
4
|
-
computed,
|
|
5
|
-
input,
|
|
6
|
-
output,
|
|
7
|
-
signal,
|
|
8
|
-
} from '@angular/core';
|
|
9
|
-
|
|
10
|
-
export type PosLoginIntent =
|
|
11
|
-
| { readonly type: 'login.key'; readonly key: string }
|
|
12
|
-
| { readonly type: 'login.backspace' }
|
|
13
|
-
| { readonly type: 'login.next'; readonly clerkNumber: string }
|
|
14
|
-
| { readonly type: 'login.submit'; readonly clerkNumber: string; readonly password: string }
|
|
15
|
-
| { readonly type: 'login.back' };
|
|
16
|
-
|
|
17
|
-
/** Display-only login/session state from the host envelope — not an API DTO. */
|
|
18
|
-
export type PosLoginSessionStatus = 'signedOut' | 'busy' | 'signedIn';
|
|
19
|
-
|
|
20
|
-
export interface PosLoginSessionState {
|
|
21
|
-
readonly status: PosLoginSessionStatus;
|
|
22
|
-
readonly displayName?: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const DEFAULT_SESSION: PosLoginSessionState = { status: 'signedOut' };
|
|
26
|
-
|
|
27
|
-
type LoginStage = 'clerkNumber' | 'password';
|
|
28
|
-
|
|
29
|
-
const KEYPAD_ROWS: readonly (readonly string[])[] = [
|
|
30
|
-
['1', '2', '3'],
|
|
31
|
-
['4', '5', '6'],
|
|
32
|
-
['7', '8', '9'],
|
|
33
|
-
['0', '.', '←'],
|
|
34
|
-
];
|
|
35
|
-
|
|
36
|
-
@Component({
|
|
37
|
-
selector: 'pos-login',
|
|
38
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
39
|
-
templateUrl: './pos-login.html',
|
|
40
|
-
styleUrl: './pos-login.scss',
|
|
41
|
-
host: {
|
|
42
|
-
class: 'pos-login',
|
|
43
|
-
},
|
|
44
|
-
})
|
|
45
|
-
export class PosLogin {
|
|
46
|
-
readonly errorMessage = input('');
|
|
47
|
-
readonly session = input<PosLoginSessionState>(DEFAULT_SESSION);
|
|
48
|
-
|
|
49
|
-
readonly titleClerkNumber = input('Enter Clerk Number');
|
|
50
|
-
readonly titleClerkPassword = input('Enter Clerk Password');
|
|
51
|
-
readonly nextLabel = input('Next');
|
|
52
|
-
readonly loginLabel = input('Login');
|
|
53
|
-
|
|
54
|
-
readonly action = output<PosLoginIntent>();
|
|
55
|
-
|
|
56
|
-
protected readonly keypadRows = KEYPAD_ROWS;
|
|
57
|
-
protected readonly inputValue = signal('');
|
|
58
|
-
protected readonly stage = signal<LoginStage>('clerkNumber');
|
|
59
|
-
private readonly clerkNumber = signal('');
|
|
60
|
-
|
|
61
|
-
protected readonly title = computed(() =>
|
|
62
|
-
this.stage() === 'clerkNumber'
|
|
63
|
-
? this.titleClerkNumber()
|
|
64
|
-
: this.titleClerkPassword(),
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
protected readonly maskInput = computed(() => this.stage() === 'password');
|
|
68
|
-
|
|
69
|
-
protected readonly showBack = computed(() => this.stage() === 'password');
|
|
70
|
-
|
|
71
|
-
protected readonly primaryLabel = computed(() =>
|
|
72
|
-
this.stage() === 'password' ? this.loginLabel() : this.nextLabel(),
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
protected readonly primaryEnabled = computed(
|
|
76
|
-
() => this.inputValue().length > 0 && !this.interactionLocked(),
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
protected readonly interactionLocked = computed(() => {
|
|
80
|
-
const status = this.session().status;
|
|
81
|
-
return status === 'busy' || status === 'signedIn';
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
protected readonly sessionStatusText = computed(() => {
|
|
85
|
-
const current = this.session();
|
|
86
|
-
if (current.status === 'busy') {
|
|
87
|
-
return 'Signing in…';
|
|
88
|
-
}
|
|
89
|
-
if (current.status === 'signedIn') {
|
|
90
|
-
return current.displayName
|
|
91
|
-
? `Signed in as ${current.displayName}`
|
|
92
|
-
: 'Signed in';
|
|
93
|
-
}
|
|
94
|
-
return '';
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
protected onKeypad(key: string): void {
|
|
98
|
-
if (this.interactionLocked()) {
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (key === '←') {
|
|
103
|
-
this.inputValue.update((value) => value.slice(0, -1));
|
|
104
|
-
this.action.emit({ type: 'login.backspace' });
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (this.inputValue().length >= 12) {
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
this.inputValue.update((value) => value + key);
|
|
113
|
-
this.action.emit({ type: 'login.key', key });
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
protected onPrimary(): void {
|
|
117
|
-
if (this.interactionLocked()) {
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
const value = this.inputValue();
|
|
122
|
-
if (!value) {
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (this.stage() === 'clerkNumber') {
|
|
127
|
-
this.clerkNumber.set(value);
|
|
128
|
-
this.inputValue.set('');
|
|
129
|
-
this.stage.set('password');
|
|
130
|
-
this.action.emit({ type: 'login.next', clerkNumber: value });
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
this.action.emit({
|
|
135
|
-
type: 'login.submit',
|
|
136
|
-
clerkNumber: this.clerkNumber(),
|
|
137
|
-
password: value,
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
protected onBack(): void {
|
|
142
|
-
if (this.interactionLocked()) {
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
this.inputValue.set(this.clerkNumber());
|
|
147
|
-
this.stage.set('clerkNumber');
|
|
148
|
-
this.action.emit({ type: 'login.back' });
|
|
149
|
-
}
|
|
150
|
-
}
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
<section
|
|
2
|
-
class="pos-receipt__panel"
|
|
3
|
-
[attr.aria-busy]="disabled() || !document().receiptReady ? true : null"
|
|
4
|
-
>
|
|
5
|
-
<header class="pos-receipt__header">
|
|
6
|
-
<div class="pos-receipt__heading">
|
|
7
|
-
<p class="pos-receipt__kicker">Adaptive POS</p>
|
|
8
|
-
<h2 class="pos-receipt__title">{{ title() }}</h2>
|
|
9
|
-
</div>
|
|
10
|
-
@if (hasDocumentNumber()) {
|
|
11
|
-
<p class="pos-receipt__number" aria-label="Document number">
|
|
12
|
-
{{ document().number }}
|
|
13
|
-
</p>
|
|
14
|
-
}
|
|
15
|
-
</header>
|
|
16
|
-
|
|
17
|
-
<p class="pos-receipt__status" aria-live="polite">{{ statusText() }}</p>
|
|
18
|
-
|
|
19
|
-
<ul class="pos-receipt__lines" aria-label="Receipt lines">
|
|
20
|
-
@if (!hasLines()) {
|
|
21
|
-
<li class="pos-receipt__empty">{{ emptyMessage() }}</li>
|
|
22
|
-
} @else {
|
|
23
|
-
@for (line of lines(); track line.lineId) {
|
|
24
|
-
<li>
|
|
25
|
-
<article
|
|
26
|
-
class="pos-receipt__line"
|
|
27
|
-
[class.pos-receipt__line--voided]="line.struckThrough"
|
|
28
|
-
>
|
|
29
|
-
<div class="pos-receipt__line-info">
|
|
30
|
-
<h3 class="pos-receipt__line-name">{{ line.name }}</h3>
|
|
31
|
-
<p class="pos-receipt__line-sku">{{ line.sku }}</p>
|
|
32
|
-
@if (line.struckThrough) {
|
|
33
|
-
<p class="pos-receipt__line-voided">Voided</p>
|
|
34
|
-
}
|
|
35
|
-
</div>
|
|
36
|
-
<p class="pos-receipt__qty">{{ line.qty }}</p>
|
|
37
|
-
<div class="pos-receipt__line-prices">
|
|
38
|
-
<p class="pos-receipt__unit-price">
|
|
39
|
-
{{ formatMoney(line.unitPrice) }}
|
|
40
|
-
</p>
|
|
41
|
-
<p class="pos-receipt__line-total">
|
|
42
|
-
{{ formatMoney(line.lineTotal) }}
|
|
43
|
-
</p>
|
|
44
|
-
</div>
|
|
45
|
-
</article>
|
|
46
|
-
</li>
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
</ul>
|
|
50
|
-
|
|
51
|
-
<div class="pos-receipt__summary">
|
|
52
|
-
<dl class="pos-receipt__totals" aria-live="polite">
|
|
53
|
-
<div class="pos-receipt__row">
|
|
54
|
-
<dt>Gross</dt>
|
|
55
|
-
<dd>{{ formatMoney(totals().gross) }}</dd>
|
|
56
|
-
</div>
|
|
57
|
-
<div class="pos-receipt__row">
|
|
58
|
-
<dt>Net</dt>
|
|
59
|
-
<dd>{{ formatMoney(totals().net) }}</dd>
|
|
60
|
-
</div>
|
|
61
|
-
<div class="pos-receipt__row">
|
|
62
|
-
<dt>VAT</dt>
|
|
63
|
-
<dd>{{ formatMoney(totals().vat) }}</dd>
|
|
64
|
-
</div>
|
|
65
|
-
<div class="pos-receipt__row">
|
|
66
|
-
<dt>Discount</dt>
|
|
67
|
-
<dd>{{ formatMoney(totals().discount) }}</dd>
|
|
68
|
-
</div>
|
|
69
|
-
<div class="pos-receipt__row pos-receipt__row--due">
|
|
70
|
-
<dt>Due</dt>
|
|
71
|
-
<dd>{{ formatMoney(totals().due) }}</dd>
|
|
72
|
-
</div>
|
|
73
|
-
</dl>
|
|
74
|
-
|
|
75
|
-
@if (hasPayments()) {
|
|
76
|
-
<section class="pos-receipt__tender">
|
|
77
|
-
<h3 class="pos-receipt__tender-title">{{ tenderLabel() }}</h3>
|
|
78
|
-
<ul class="pos-receipt__payments">
|
|
79
|
-
@for (payment of payments(); track $index) {
|
|
80
|
-
<li class="pos-receipt__payment">
|
|
81
|
-
<header class="pos-receipt__payment-header">
|
|
82
|
-
<p class="pos-receipt__payment-tender">{{ payment.tender }}</p>
|
|
83
|
-
<p class="pos-receipt__payment-state">{{ payment.state }}</p>
|
|
84
|
-
</header>
|
|
85
|
-
<dl class="pos-receipt__payment-amounts">
|
|
86
|
-
<div class="pos-receipt__row">
|
|
87
|
-
<dt>{{ amountLabel() }}</dt>
|
|
88
|
-
<dd>{{ formatMoney(payment.amount) }}</dd>
|
|
89
|
-
</div>
|
|
90
|
-
<div class="pos-receipt__row">
|
|
91
|
-
<dt>{{ tenderedLabel() }}</dt>
|
|
92
|
-
<dd>{{ formatMoney(payment.tendered) }}</dd>
|
|
93
|
-
</div>
|
|
94
|
-
<div class="pos-receipt__row pos-receipt__row--change">
|
|
95
|
-
<dt>{{ changeLabel() }}</dt>
|
|
96
|
-
<dd>{{ formatMoney(payment.change) }}</dd>
|
|
97
|
-
</div>
|
|
98
|
-
</dl>
|
|
99
|
-
</li>
|
|
100
|
-
}
|
|
101
|
-
</ul>
|
|
102
|
-
</section>
|
|
103
|
-
}
|
|
104
|
-
</div>
|
|
105
|
-
|
|
106
|
-
<div class="pos-receipt__actions">
|
|
107
|
-
<button
|
|
108
|
-
type="button"
|
|
109
|
-
class="pos-receipt__new-sale"
|
|
110
|
-
[disabled]="!canStartNewSale()"
|
|
111
|
-
(click)="onNewSale()"
|
|
112
|
-
>
|
|
113
|
-
{{ newSaleLabel() }}
|
|
114
|
-
</button>
|
|
115
|
-
</div>
|
|
116
|
-
</section>
|