@7365admin1/layer-common 3.2.2-staging.106 → 3.2.2-staging.108

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,22 +1,12 @@
1
1
  <template>
2
- <v-card class="pa-2">
2
+ <v-card class="pa-2 pb-0">
3
3
  <!-- Header -->
4
- <v-card-title class="d-flex align-center justify-space-between pb-2">
4
+ <v-card-title class="pb-2">
5
5
  <span class="text-h6 font-weight-bold">
6
6
  {{ isEditing ? "Edit Plan" : "Create New Plan" }}
7
7
  </span>
8
-
9
- <v-btn
10
- icon="mdi-close"
11
- variant="text"
12
- density="comfortable"
13
- aria-label="Close"
14
- @click="$emit('cancel')"
15
- />
16
8
  </v-card-title>
17
9
 
18
- <v-divider />
19
-
20
10
  <!-- Form Content -->
21
11
  <v-form ref="formRef" class="my-4" @submit.prevent="submit">
22
12
  <v-card-text class="pa-2 max-h-70vh overflow-y-auto">
@@ -68,8 +58,6 @@
68
58
  </template>
69
59
  </v-text-field>
70
60
 
71
- <v-divider class="my-4" />
72
-
73
61
  <!-- Pricing Section -->
74
62
  <div class="text-subtitle-2 font-weight-bold mb-3 text-grey-darken-3">
75
63
  Pricing
@@ -116,14 +104,14 @@
116
104
  selected-class="bg-grey-darken-3 text-white"
117
105
  >
118
106
  <v-chip
119
- v-for="app in AVAILABLE_APPLICATIONS"
120
- :key="app.id"
121
- :value="app.id"
107
+ v-for="app in APP_LIST"
108
+ :key="app.value"
109
+ :value="app.value"
122
110
  filter
123
111
  variant="outlined"
124
112
  size="small"
125
113
  >
126
- {{ app.name }}
114
+ {{ app.title }}
127
115
  </v-chip>
128
116
  </v-chip-group>
129
117
  </div>
@@ -155,26 +143,37 @@
155
143
  <v-card variant="outlined" class="pa-1 mb-3">
156
144
  <v-list density="compact" class="py-0">
157
145
  <v-list-item
158
- v-for="app in AVAILABLE_APPLICATIONS"
159
- :key="app.id"
146
+ v-for="app in APP_LIST"
147
+ :key="app.value"
160
148
  class="px-2"
161
149
  >
162
150
  <template #prepend>
163
151
  <v-checkbox-btn
164
- :model-value="form.customApps.includes(app.id)"
152
+ :model-value="form.customApps.includes(app.value)"
165
153
  density="compact"
166
- @update:model-value="toggleApp(form.customApps, app.id)"
154
+ @update:model-value="toggleApp(form.customApps, app.value)"
167
155
  />
168
156
  </template>
169
157
 
170
158
  <v-list-item-title class="text-body-2">
171
- {{ app.name }}
159
+ {{ app.title }}
172
160
  </v-list-item-title>
173
161
 
174
162
  <template #append>
175
- <span class="text-caption text-grey">
176
- ${{ form.billingCycle === 'annually' ? app.yearlyPrice : app.monthlyPrice }}/{{ form.billingCycle === 'annually' ? 'yr' : 'mo' }}
177
- </span>
163
+ <div class="d-flex align-center" style="gap: 4px">
164
+ <span class="text-caption text-grey">$</span>
165
+ <v-text-field
166
+ v-model.number="form.customAppPrices[app.value]"
167
+ type="number"
168
+ min="0"
169
+ density="compact"
170
+ variant="outlined"
171
+ hide-details
172
+ style="width: 84px"
173
+ :disabled="!form.customApps.includes(app.value)"
174
+ />
175
+ <span class="text-caption text-grey">/{{ form.billingCycle === 'annually' ? 'yr' : 'mo' }}</span>
176
+ </div>
178
177
  </template>
179
178
  </v-list-item>
180
179
  </v-list>
@@ -211,28 +210,21 @@
211
210
  </v-alert>
212
211
  </v-card-text>
213
212
 
214
- <v-divider />
215
-
216
213
  <!-- Footer Buttons -->
217
- <v-card-actions class="pt-3 px-2">
218
- <v-btn
219
- variant="outlined"
220
- color="grey-darken-1"
221
- class="flex-1 text-none"
222
- @click="$emit('cancel')"
223
- >
224
- Cancel
225
- </v-btn>
226
-
227
- <v-btn
228
- type="submit"
229
- color="grey-darken-4"
230
- variant="flat"
231
- class="flex-1 text-none"
232
- >
233
- {{ isEditing ? "Save Changes" : "Create Plan" }}
234
- </v-btn>
235
- </v-card-actions>
214
+ <v-toolbar density="compact">
215
+ <v-row no-gutters>
216
+ <v-col cols="6">
217
+ <v-btn tile block variant="text" class="text-none" size="48" @click="$emit('cancel')">
218
+ Cancel
219
+ </v-btn>
220
+ </v-col>
221
+ <v-col cols="6">
222
+ <v-btn tile block type="submit" variant="flat" color="primary" class="text-none" size="48">
223
+ {{ isEditing ? "Save Changes" : "Create Plan" }}
224
+ </v-btn>
225
+ </v-col>
226
+ </v-row>
227
+ </v-toolbar>
236
228
  </v-form>
237
229
  </v-card>
238
230
  </template>
@@ -241,7 +233,9 @@
241
233
  import { reactive, computed, watch } from "vue";
242
234
 
243
235
  import {
244
- AVAILABLE_APPLICATIONS,
236
+ APP_LIST,
237
+ STATIC_APP_MONTHLY_PRICE,
238
+ STATIC_APP_YEARLY_PRICE,
245
239
  type PlanFormState,
246
240
  type SubscriptionPlan,
247
241
  } from "../types/subscription";
@@ -284,6 +278,14 @@ const form = reactive<PlanFormState>({
284
278
  props.initialPlan?.planType === "custom_apps"
285
279
  ? [...(props.initialPlan?.applicationIds ?? [])]
286
280
  : [],
281
+ customAppPrices: Object.fromEntries(
282
+ APP_LIST.map((app) => [
283
+ app.value,
284
+ props.initialPlan?.billingCycle === "annually"
285
+ ? STATIC_APP_YEARLY_PRICE
286
+ : STATIC_APP_MONTHLY_PRICE,
287
+ ])
288
+ ),
287
289
  });
288
290
 
289
291
  const errors = reactive({
@@ -304,6 +306,29 @@ watch(
304
306
  }
305
307
  );
306
308
 
309
+ function staticPriceForCycle(cycle: typeof form.billingCycle) {
310
+ return cycle === "annually" ? STATIC_APP_YEARLY_PRICE : STATIC_APP_MONTHLY_PRICE;
311
+ }
312
+
313
+ /*
314
+ * Keep custom app prices in sync with the billing cycle, but only for
315
+ * apps still sitting at the previous cycle's default (leave manual edits alone)
316
+ */
317
+ watch(
318
+ () => form.billingCycle,
319
+ (newCycle, oldCycle) => {
320
+ const oldDefault = staticPriceForCycle(oldCycle);
321
+ const newDefault = staticPriceForCycle(newCycle);
322
+ if (oldDefault === newDefault) return;
323
+
324
+ for (const app of APP_LIST) {
325
+ if (form.customAppPrices[app.value] === oldDefault) {
326
+ form.customAppPrices[app.value] = newDefault;
327
+ }
328
+ }
329
+ }
330
+ );
331
+
307
332
  /*
308
333
  * Toggle application selection helper
309
334
  */
@@ -320,15 +345,10 @@ function toggleApp(list: string[], id: string) {
320
345
  * Custom app total price calculation
321
346
  */
322
347
  const customTotalPrice = computed(() => {
323
- return form.customApps.reduce((total, id) => {
324
- const app = AVAILABLE_APPLICATIONS.find((item) => item.id === id);
325
- if (!app) return total;
326
-
327
- return (
328
- total +
329
- (form.billingCycle === "annually" ? app.yearlyPrice : app.monthlyPrice)
330
- );
331
- }, 0);
348
+ return form.customApps.reduce(
349
+ (total, value) => total + (form.customAppPrices[value] ?? 0),
350
+ 0
351
+ );
332
352
  });
333
353
 
334
354
  /*
@@ -340,6 +360,11 @@ function validate(): boolean {
340
360
 
341
361
  if (form.planType !== "free_bundle" && !form.billingCycle) {
342
362
  errors.pricing = "Please select a billing cycle.";
363
+ } else if (
364
+ (form.planType === "free_bundle" || form.planType === "paid_bundle") &&
365
+ form.bundleApps.length === 0
366
+ ) {
367
+ errors.pricing = "Select at least one application.";
343
368
  } else if (
344
369
  form.planType === "paid_bundle" &&
345
370
  (!form.bundlePrice || form.bundlePrice <= 0)
@@ -166,8 +166,28 @@
166
166
  </template>
167
167
 
168
168
  <template v-if="passType === 'HID_QR'">
169
+ <v-card variant="tonal" color="primary" class="mt-3 pa-3" rounded="lg">
170
+ <div class="d-flex align-center ga-3">
171
+ <v-icon icon="mdi-door-open" size="28" />
172
+ <div>
173
+ <div class="text-caption text-medium-emphasis">Access Reader</div>
174
+ <div class="text-subtitle-2 font-weight-bold">{{ hidReaderName || "Reader not configured" }}</div>
175
+ <div class="text-caption mt-1">Access Portal: {{ hidPortalName || "Portal not configured" }}</div>
176
+ </div>
177
+ </div>
178
+ </v-card>
179
+ <v-alert
180
+ v-if="!hidPortalName"
181
+ type="warning"
182
+ variant="tonal"
183
+ density="compact"
184
+ class="mt-3"
185
+ icon="mdi-alert"
186
+ >
187
+ Reconnect this HID reader and select a portal before generating a visitor QR code.
188
+ </v-alert>
169
189
  <v-alert
170
- v-if="!hidQrPrinterConfigured"
190
+ v-else-if="!hidQrPrinterConfigured"
171
191
  type="info"
172
192
  variant="tonal"
173
193
  density="compact"
@@ -283,6 +303,14 @@ const props = defineProps({
283
303
  type: Boolean,
284
304
  default: false,
285
305
  },
306
+ hidReaderName: {
307
+ type: String,
308
+ default: "",
309
+ },
310
+ hidPortalName: {
311
+ type: String,
312
+ default: "",
313
+ },
286
314
  excludedCardIds: {
287
315
  type: Array as PropType<string[]>,
288
316
  default: () => [],
@@ -12,7 +12,7 @@
12
12
  </v-btn>
13
13
 
14
14
  <v-select
15
- v-if="readers.length > 1"
15
+ v-if="readers.length"
16
16
  v-model="selectedReaderId"
17
17
  :items="readerOptions"
18
18
  item-title="title"
@@ -412,7 +412,7 @@ const accessMethodOptions = ["All", "Facial", "QR Code", "ID and Password", "PIN
412
412
 
413
413
  const readerOptions = computed(() =>
414
414
  readers.value.map((reader) => ({
415
- title: reader.name || reader.deviceId || reader._id,
415
+ title: `${reader.name || reader.deviceId || reader._id} — ${reader.portalName || "Portal not configured"}`,
416
416
  value: reader._id,
417
417
  })),
418
418
  );
@@ -582,7 +582,6 @@ async function loadDashboard() {
582
582
  loading.value = true;
583
583
  resetDashboardData();
584
584
  try {
585
- const isAdministratorTab = activeTab.value === "administrator";
586
585
  const isVisitorTab = activeTab.value === "visitor";
587
586
  const [identityResponse, logResponse, hidAccessLogs, configurationResponse, roleResponse, visitorResponse, readerUserResponse] = await Promise.all([
588
587
  loadDashboardSource(
@@ -603,9 +602,7 @@ async function loadDashboard() {
603
602
  ),
604
603
  loadDashboardSource(loadLiveAccessLogs(selectedReaderId.value), [], "live access logs"),
605
604
  loadDashboardSource(loadReaderConfiguration(selectedReaderId.value), {}, "reader configuration"),
606
- isAdministratorTab
607
- ? loadDashboardSource(loadAdministratorRoles(selectedReaderId.value), null, "administrator roles")
608
- : Promise.resolve(null),
605
+ loadDashboardSource(loadAdministratorRoles(selectedReaderId.value), null, "administrator roles"),
609
606
  isVisitorTab && props.org
610
607
  ? loadDashboardSource(
611
608
  getVisitors({
@@ -632,14 +629,9 @@ async function loadDashboard() {
632
629
  );
633
630
  administratorUserIds.value = getAdministratorRoleUserIds(roleResponse);
634
631
  allIdentities.value = mergedIdentities;
635
- identities.value = isAdministratorTab
636
- ? mergedIdentities.filter((identity: Record<string, any>) => {
637
- const hidUserId = toHidNumericId(identity.hidUserId);
638
- return Boolean(hidUserId && administratorUserIds.value.has(hidUserId));
639
- })
640
- : activeTab.value === "visitor"
641
- ? mergedIdentities.filter(isVisitorIdentity)
642
- : mergedIdentities.filter((identity: Record<string, any>) => !isVisitorIdentity(identity));
632
+ identities.value = mergedIdentities.filter(
633
+ (identity: Record<string, any>) => classifyIdentity(identity) === activeTab.value,
634
+ );
643
635
  logs.value = logResponse?.items ?? logResponse?.data?.items ?? logResponse?.data?.logs ?? [];
644
636
  liveAccessLogs.value = hidAccessLogs;
645
637
  readerConfiguration.value = normalizeReaderConfiguration(configurationResponse);
@@ -781,8 +773,10 @@ function normalizeHidUsers(response: Record<string, any>) {
781
773
  registration: user.registration,
782
774
  cardNo: user.card_value || user.cardNo || "",
783
775
  name: user.name,
776
+ hidUserTypeId: user.user_type_id,
784
777
  metadata: {
785
778
  name: user.name,
779
+ hidUserTypeId: user.user_type_id,
786
780
  imageTimestamp: user.image_timestamp ? String(user.image_timestamp) : "",
787
781
  hidReaderSource: true,
788
782
  },
@@ -835,21 +829,22 @@ function resetDashboardData() {
835
829
 
836
830
  function isVisibleForActiveTab(row: AccessRow) {
837
831
  const identity = findIdentityByKey(row.identityKey);
838
- const isVisitor = row.identityType === "visitor" || Boolean(row.visitorId) || isVisitorIdentity(identity);
839
- if (activeTab.value === "visitor") {
840
- return isVisitor;
841
- }
842
-
843
- if (activeTab.value === "administrator") {
844
- const hidUserId = toHidNumericId(identity?.hidUserId ?? row.identityKey.split("|")[0]);
845
- return Boolean(hidUserId && administratorUserIds.value.has(hidUserId));
846
- }
847
-
848
- return !isVisitor;
832
+ return classifyIdentity(identity, row) === activeTab.value;
849
833
  }
850
834
 
851
835
  function isVisitorIdentity(identity: Record<string, unknown> | undefined) {
852
- return Boolean(identity && (identity.type === "visitor" || identity.visitor));
836
+ return Boolean(identity && (
837
+ identity.type === "visitor"
838
+ || identity.visitor
839
+ || Number(identity.hidUserTypeId ?? (identity.metadata as Record<string, unknown> | undefined)?.hidUserTypeId) === 1
840
+ ));
841
+ }
842
+
843
+ function classifyIdentity(identity?: Record<string, any>, row?: AccessRow): AccessTab {
844
+ const hidUserId = toHidNumericId(identity?.hidUserId ?? row?.identityKey.split("|")[0]);
845
+ if (hidUserId && administratorUserIds.value.has(hidUserId)) return "administrator";
846
+ if (row?.identityType === "visitor" || row?.visitorId || isVisitorIdentity(identity)) return "visitor";
847
+ return "resident";
853
848
  }
854
849
 
855
850
  function loadAdministratorRoles(readerId: string) {
@@ -1200,7 +1195,18 @@ function getRawAccessLogs(event: Record<string, any>) {
1200
1195
  payload.access_logs ||
1201
1196
  payload.accessLogs;
1202
1197
 
1203
- return Array.isArray(accessLogs) ? accessLogs : [];
1198
+ if (Array.isArray(accessLogs)) return accessLogs;
1199
+
1200
+ const objectChanges = payload.object_changes || result.object_changes || [];
1201
+ return Array.isArray(objectChanges)
1202
+ ? objectChanges
1203
+ .filter((change: Record<string, any>) => (
1204
+ change?.object === "access_logs"
1205
+ && change?.type !== "deleted"
1206
+ && change?.values
1207
+ ))
1208
+ .map((change: Record<string, any>) => change.values)
1209
+ : [];
1204
1210
  }
1205
1211
 
1206
1212
  function mapAccessLogToRow(rawLog: Record<string, any>, event: Record<string, any>, index: string): AccessRow {
@@ -1212,7 +1218,9 @@ function mapAccessLogToRow(rawLog: Record<string, any>, event: Record<string, an
1212
1218
  return {
1213
1219
  key: `${event._id || event.id || "log"}-${rawLog.id || rawLog.time || index}`,
1214
1220
  identityKey,
1215
- identityType: String(identity?.type || eventIdentity.type || "").toLowerCase() || undefined,
1221
+ identityType: identity
1222
+ ? classifyIdentity(identity)
1223
+ : String(eventIdentity.type || "").toLowerCase() || undefined,
1216
1224
  visitorId: String(identity?.visitor || eventIdentity.visitor || "") || undefined,
1217
1225
  name: identity ? getName(identity) : getRawLogName(rawLog, event),
1218
1226
  facialData: identity ? getFacialData(identity) : "N/A",
@@ -1276,7 +1284,12 @@ function findIdentityForAccessLog(rawLog: Record<string, any>, event: Record<str
1276
1284
  const eventIdentityId = event.payload?.identity?._id;
1277
1285
  if (eventIdentityId) {
1278
1286
  const byId = allIdentities.value.find((identity) => String(identity._id) === String(eventIdentityId));
1279
- if (byId) return byId;
1287
+ const rawUserId = rawLog.user_id ?? rawLog.userId ?? rawLog.hidUserId;
1288
+ if (byId && (
1289
+ rawUserId === undefined
1290
+ || rawUserId === null
1291
+ || String(byId.hidUserId) === String(rawUserId)
1292
+ )) return byId;
1280
1293
  }
1281
1294
 
1282
1295
  const hidUserId = rawLog.user_id ?? rawLog.userId ?? rawLog.hidUserId ?? event.payload?.identity?.hidUserId ?? event.payload?.identityLookup?.hidUserId;
@@ -24,6 +24,19 @@
24
24
  {{ webPhone.callState.value === 'incoming' ? 'Incoming Call' : 'Web Call' }}
25
25
  </v-btn>
26
26
 
27
+ <v-select
28
+ v-if="readers.length"
29
+ v-model="selectedReaderId"
30
+ :items="readerOptions"
31
+ item-title="title"
32
+ item-value="value"
33
+ variant="outlined"
34
+ density="compact"
35
+ hide-details
36
+ class="reader-input"
37
+ @update:model-value="page = 1"
38
+ />
39
+
27
40
  <div class="toolbar-spacer" />
28
41
 
29
42
  <v-text-field
@@ -702,6 +715,7 @@ const {
702
715
 
703
716
  const activeTab = ref<IntercomTab>("intercom");
704
717
  const readers = ref<Record<string, any>[]>([]);
718
+ const selectedReaderId = ref("");
705
719
  const intercomRows = ref<IntercomRow[]>([]);
706
720
  const contacts = ref<ContactRow[]>([]);
707
721
  const loading = ref(false);
@@ -821,6 +835,11 @@ const contactForm = reactive({
821
835
  serverAddress: "",
822
836
  });
823
837
 
838
+ const readerOptions = computed(() => readers.value.map((reader) => ({
839
+ title: `${reader.name || reader.deviceId || reader._id} — ${reader.portalName || "Portal not configured"}`,
840
+ value: reader._id,
841
+ })));
842
+
824
843
  const visibleRows = computed(() => activeTab.value === "intercom" ? filteredIntercomRows.value : filteredContacts.value);
825
844
  const pages = computed(() => Math.max(1, Math.ceil(visibleRows.value.length / limit)));
826
845
  const pageRange = computed(() => {
@@ -834,6 +853,7 @@ const pageRange = computed(() => {
834
853
  const filteredIntercomRows = computed(() => {
835
854
  const query = search.value.trim().toLowerCase();
836
855
  return intercomRows.value.filter((row) => {
856
+ const matchesReader = !selectedReaderId.value || row.reader._id === selectedReaderId.value;
837
857
  const matchesSearch = !query || [
838
858
  row.name,
839
859
  row.location,
@@ -843,18 +863,22 @@ const filteredIntercomRows = computed(() => {
843
863
  row.status,
844
864
  ].some((value) => String(value || "").toLowerCase().includes(query));
845
865
  const matchesStatus = statusFilter.value === "Status" || row.status === statusFilter.value;
846
- return matchesSearch && matchesStatus;
866
+ return matchesReader && matchesSearch && matchesStatus;
847
867
  });
848
868
  });
849
869
 
850
870
  const filteredContacts = computed(() => {
851
871
  const query = search.value.trim().toLowerCase();
852
- return contacts.value.filter((contact) => !query || [
853
- contact.name,
854
- contact.location,
855
- contact.serverAddress,
856
- contact.dialCode,
857
- ].some((value) => String(value || "").toLowerCase().includes(query)));
872
+ return contacts.value.filter((contact) => {
873
+ const matchesReader = !selectedReaderId.value || contact.readerId === selectedReaderId.value;
874
+ const matchesSearch = !query || [
875
+ contact.name,
876
+ contact.location,
877
+ contact.serverAddress,
878
+ contact.dialCode,
879
+ ].some((value) => String(value || "").toLowerCase().includes(query));
880
+ return matchesReader && matchesSearch;
881
+ });
858
882
  });
859
883
 
860
884
  const paginatedIntercomRows = computed(() => paginate(filteredIntercomRows.value));
@@ -977,6 +1001,9 @@ async function reload() {
977
1001
  try {
978
1002
  const response = await getReaders({ site: props.site, page: 1, limit: 100 });
979
1003
  readers.value = response?.items ?? response?.data?.items ?? response?.data?.readers ?? [];
1004
+ if (!readers.value.some((reader) => reader._id === selectedReaderId.value)) {
1005
+ selectedReaderId.value = readers.value[0]?._id || "";
1006
+ }
980
1007
  await loadIntercomRows();
981
1008
  await loadContacts();
982
1009
  } catch (error: any) {
@@ -1328,9 +1355,10 @@ function toggleWebMute() {
1328
1355
  function openContactDialog(contact?: ContactRow) {
1329
1356
  selectedContact.value = contact || null;
1330
1357
  contactForm.id = contact?.id;
1331
- contactForm.readerId = contact?.readerId || readers.value[0]?._id || "";
1358
+ contactForm.readerId = contact?.readerId || selectedReaderId.value || readers.value[0]?._id || "";
1332
1359
  contactForm.name = contact?.name === "N/A" ? "" : contact?.name || "";
1333
- contactForm.location = contact?.location === "N/A" ? "" : contact?.location || readers.value[0]?.location || "";
1360
+ const selectedReader = readers.value.find((reader) => reader._id === contactForm.readerId);
1361
+ contactForm.location = contact?.location === "N/A" ? "" : contact?.location || selectedReader?.location || "";
1334
1362
  contactForm.serverAddress = contact?.serverAddress === "N/A" ? "" : contact?.serverAddress || "";
1335
1363
  contactDialog.value = true;
1336
1364
  }
@@ -1695,6 +1723,11 @@ export default {
1695
1723
  max-width: 220px;
1696
1724
  }
1697
1725
 
1726
+ .reader-input {
1727
+ width: 300px;
1728
+ max-width: 36vw;
1729
+ }
1730
+
1698
1731
  .status-input {
1699
1732
  max-width: 160px;
1700
1733
  }
@@ -2673,6 +2706,7 @@ export default {
2673
2706
  }
2674
2707
 
2675
2708
  .top-placeholder,
2709
+ .reader-input,
2676
2710
  .search-input,
2677
2711
  .status-input {
2678
2712
  width: 100%;
@@ -501,7 +501,6 @@ const identificationMethodOptions: Array<{
501
501
  { key: "qrCode", label: "QR Code" },
502
502
  { key: "idPassword", label: "ID Password" },
503
503
  { key: "pin", label: "PIN" },
504
- { key: "bluetooth", label: "Bluetooth" },
505
504
  ];
506
505
  const accessPermissionOptions: Array<{
507
506
  category: THidPermissionCategory;
@@ -590,7 +589,7 @@ async function loadConfig() {
590
589
  qrCode: configuredMethods?.qrCode ?? Boolean(config.enabled),
591
590
  idPassword: configuredMethods?.idPassword ?? true,
592
591
  pin: configuredMethods?.pin ?? false,
593
- bluetooth: configuredMethods?.bluetooth ?? false,
592
+ bluetooth: false,
594
593
  },
595
594
  printer: {
596
595
  vendorId: String(config.printer?.vendorId || ""),