@7365admin1/layer-common 3.2.2-staging.84 → 3.2.2-staging.86

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.
@@ -33,7 +33,7 @@ type HidVisitorQrData = {
33
33
  readerId: string;
34
34
  hidUserId: number;
35
35
  qrValue: string;
36
- qrFormat: "0" | "1" | "2";
36
+ qrFormat: "0";
37
37
  issuedAt: string;
38
38
  expiresAt: string;
39
39
  };
@@ -120,7 +120,7 @@ export default function useHidAmico() {
120
120
 
121
121
  function issueVisitorQr(
122
122
  readerId: string,
123
- payload: { visitorId: string; validityMinutes: number; qrFormat: "0" | "1" | "2" },
123
+ payload: { visitorId: string; validityMinutes: number },
124
124
  ) {
125
125
  return useNuxtApp().$api<HidVisitorQrResponse>(`${basePath}/readers/${readerId}/visitor-qr`, {
126
126
  method: "POST",
@@ -189,6 +189,27 @@ export default function useHidAmico() {
189
189
  );
190
190
  }
191
191
 
192
+ function getUserPinStatus(readerId: string, hidUserId: string | number) {
193
+ return useNuxtApp().$api<{ data: { pinEnrolled: boolean } }>(
194
+ `${basePath}/readers/${readerId}/users/${hidUserId}/pin`,
195
+ { method: "GET" },
196
+ );
197
+ }
198
+
199
+ function setUserPin(readerId: string, hidUserId: string | number, pin: string) {
200
+ return useNuxtApp().$api<{ data: { pinEnrolled: boolean } }>(
201
+ `${basePath}/readers/${readerId}/users/${hidUserId}/pin`,
202
+ { method: "PUT", body: { pin } },
203
+ );
204
+ }
205
+
206
+ function deleteUserPin(readerId: string, hidUserId: string | number) {
207
+ return useNuxtApp().$api<{ data: { pinEnrolled: boolean } }>(
208
+ `${basePath}/readers/${readerId}/users/${hidUserId}/pin`,
209
+ { method: "DELETE" },
210
+ );
211
+ }
212
+
192
213
  function createIdentity(readerId: string, payload: HidIdentityPayload) {
193
214
  return useNuxtApp().$api<Record<string, any>>(`${basePath}/readers/${readerId}/identities`, {
194
215
  method: "POST",
@@ -286,6 +307,9 @@ export default function useHidAmico() {
286
307
  getUserImage,
287
308
  setUserImage,
288
309
  deleteUserImage,
310
+ getUserPinStatus,
311
+ setUserPin,
312
+ deleteUserPin,
289
313
  getIdentities,
290
314
  createIdentity,
291
315
  updateIdentity,
@@ -31,6 +31,12 @@ export default function useSipWebPhone() {
31
31
 
32
32
  const isRegistered = computed(() => connectionState.value === "registered");
33
33
  const hasCall = computed(() => callState.value !== "idle");
34
+ const audioConstraints: MediaTrackConstraints = {
35
+ echoCancellation: true,
36
+ noiseSuppression: true,
37
+ autoGainControl: true,
38
+ channelCount: 1,
39
+ };
34
40
 
35
41
  async function connect(config: SipWebPhoneConfig, media: SipWebPhoneMedia) {
36
42
  validateConfig(config);
@@ -73,6 +79,7 @@ export default function useSipWebPhone() {
73
79
  },
74
80
  onCallAnswered: () => {
75
81
  callState.value = "active";
82
+ ensureMicrophoneSending();
76
83
  syncVideoState();
77
84
  },
78
85
  onCallHangup: () => {
@@ -104,7 +111,9 @@ export default function useSipWebPhone() {
104
111
  aor: normalizeSipUri(config.aor),
105
112
  delegate,
106
113
  media: {
107
- constraints: { audio: true, video: false },
114
+ // The browser sends microphone audio and receives HID audio/video.
115
+ // Do not request a local camera merely because the HID video leg is enabled.
116
+ constraints: { audio: audioConstraints, video: false },
108
117
  remote: {
109
118
  audio: media.remoteAudio || undefined,
110
119
  video: media.remoteVideo || undefined,
@@ -167,7 +176,7 @@ export default function useSipWebPhone() {
167
176
  callState.value = "calling";
168
177
  try {
169
178
  const sessionDescriptionHandlerOptions: SessionDescriptionHandlerOptions = {
170
- constraints: { audio: true, video: false },
179
+ constraints: { audio: audioConstraints, video: false },
171
180
  offerOptions: {
172
181
  offerToReceiveAudio: true,
173
182
  offerToReceiveVideo: Boolean(activeConfig.value?.video),
@@ -184,7 +193,11 @@ export default function useSipWebPhone() {
184
193
  async function answer() {
185
194
  const user = requireRegisteredUser();
186
195
  const sessionDescriptionHandlerOptions: SessionDescriptionHandlerOptions = {
187
- constraints: { audio: true, video: false },
196
+ constraints: { audio: audioConstraints, video: false },
197
+ offerOptions: {
198
+ offerToReceiveAudio: true,
199
+ offerToReceiveVideo: Boolean(activeConfig.value?.video),
200
+ },
188
201
  };
189
202
  await user.answer({ sessionDescriptionHandlerOptions });
190
203
  }
@@ -214,6 +227,18 @@ export default function useSipWebPhone() {
214
227
  muted.value = !enable;
215
228
  }
216
229
 
230
+ function ensureMicrophoneSending() {
231
+ const tracks = simpleUser.value?.localMediaStream?.getAudioTracks() || [];
232
+ if (!tracks.length) {
233
+ errorMessage.value = "The call connected without a microphone track. Reconnect and allow microphone access.";
234
+ return;
235
+ }
236
+ tracks.forEach((track) => {
237
+ track.enabled = true;
238
+ });
239
+ muted.value = false;
240
+ }
241
+
217
242
  function syncVideoState() {
218
243
  const user = simpleUser.value;
219
244
  const remoteStream = user?.remoteMediaStream;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@7365admin1/layer-common",
3
3
  "license": "MIT",
4
4
  "type": "module",
5
- "version": "3.2.2-staging.84",
5
+ "version": "3.2.2-staging.86",
6
6
  "author": "7365admin1",
7
7
  "main": "./nuxt.config.ts",
8
8
  "publishConfig": {
@@ -53,6 +53,7 @@ import type { CookieOptions } from "#app";
53
53
  */
54
54
  import "../assets/css/tokens.css";
55
55
  import "../assets/css/shell.css";
56
+ import "../assets/css/screens.css";
56
57
 
57
58
  export default defineNuxtPlugin((app) => {
58
59
  /**
@@ -35,6 +35,42 @@ test("an unmapped status comes out neutral rather than guessed", () => {
35
35
  }
36
36
  });
37
37
 
38
+ /**
39
+ * PHASE 4. Each of these words is on a module screen today with a colour a
40
+ * per-screen `getStatusColor` gave it. The list has to keep giving it the SAME
41
+ * tone, or adopting the shared chip would quietly repaint a status.
42
+ */
43
+ test("the product's own status words keep the tone the screens already gave them", () => {
44
+ for (const s of ["Accepted", "Approved", "Resolved", "Active"]) {
45
+ assert.equal(statusTone(s), "ok", s);
46
+ }
47
+
48
+ for (const s of ["Rejected", "Deleted", "Suspended"]) {
49
+ assert.equal(statusTone(s), "err", s);
50
+ }
51
+
52
+ for (const s of ["In Progress", "In-Progress", "To-Do", "Awaiting Approval"]) {
53
+ assert.equal(statusTone(s), "warn", s);
54
+ }
55
+
56
+ for (const s of ["Ongoing", "For Review"]) {
57
+ assert.equal(statusTone(s), "info", s);
58
+ }
59
+
60
+ for (const s of ["Inactive", "Expired"]) {
61
+ assert.equal(statusTone(s), "neutral", s);
62
+ }
63
+ });
64
+
65
+ /**
66
+ * The one place the handoff and the old per-screen colour disagree: `Open` was
67
+ * grey on the cleaning schedules and the design names it `ok`. Pinned so the
68
+ * disagreement is a decision on the record rather than a surprise.
69
+ */
70
+ test("Open follows the design, not the old grey", () => {
71
+ assert.equal(statusTone("open"), "ok");
72
+ });
73
+
38
74
  test("the class name matches what tokens.css actually defines", () => {
39
75
  assert.equal(statusToneClass("Paid"), "tone-ok");
40
76
  assert.equal(statusToneClass("Whatever"), "tone-neutral");
package/utils/status.ts CHANGED
@@ -34,6 +34,42 @@ const TONES: Record<string, TStatusTone> = {
34
34
 
35
35
  role: "info",
36
36
  info: "info",
37
+
38
+ /**
39
+ * PHASE 4 - THE WORDS THIS PRODUCT ACTUALLY USES.
40
+ *
41
+ * The handoff names ten words; the module screens show more than that, and
42
+ * each of them is ALREADY painted a colour today by a per-screen
43
+ * `getStatusColor`. So these are not new opinions - each one is the tone the
44
+ * screens already give that word, moved into the one list so that "Ongoing"
45
+ * is the same blue on a cleaning schedule and on a scheduled area. Where the
46
+ * old per-screen colour and the design disagree the design wins, and there
47
+ * is exactly one of those: `Open` was grey and the handoff names it `ok`.
48
+ *
49
+ * Anything still unlisted stays neutral, deliberately - see above.
50
+ */
51
+ ongoing: "info", // was `primary`
52
+ "in progress": "warn", // was #FB8C00, orange
53
+ "for review": "info", // was `primary`
54
+ "to do": "warn", // was `orange`
55
+
56
+ accepted: "ok", // was `success`
57
+ approved: "ok",
58
+ resolved: "ok", // was #4CAF50, green
59
+ active: "ok", // was `success`
60
+
61
+ rejected: "err", // was `error`
62
+ deleted: "err", // was `error`
63
+ suspended: "err", // was `error`
64
+
65
+ complete: "ok",
66
+
67
+ inactive: "neutral", // was `grey`
68
+ "in active": "neutral", // the same word, printed "In-Active"
69
+ declined: "neutral", // was Vuetify's `default`
70
+ expired: "neutral", // was `grey`
71
+
72
+ "awaiting approval": "warn", // a waiting state, like Pending
37
73
  };
38
74
 
39
75
  /**