@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.
- package/assets/css/screens.css +471 -0
- package/components/AttendanceMain.vue +4 -3
- package/components/CleaningScheduleMain.vue +29 -46
- package/components/HidIntercomManagement.vue +40 -24
- package/components/HidQrCodeConfiguration.vue +24 -2
- package/components/HidUserEnrollment.vue +68 -31
- package/components/InvitationMain.vue +10 -8
- package/components/MemberMain.vue +9 -6
- package/components/MyAttendanceMain.vue +3 -5
- package/components/RolePermissionMain.vue +15 -10
- package/components/ScheduleTaskMain.vue +23 -26
- package/components/ServiceProviderMain.vue +17 -42
- package/components/StatusChip.vue +16 -2
- package/components/TableMain.vue +0 -133
- package/components/VisitorForm.vue +100 -7
- package/components/VisitorManagement.vue +170 -2
- package/composables/useHidAmico.ts +26 -2
- package/composables/useSipWebPhone.ts +28 -3
- package/package.json +1 -1
- package/plugins/vuetify.ts +1 -0
- package/utils/status.test.ts +36 -0
- package/utils/status.ts +36 -0
|
@@ -338,10 +338,10 @@
|
|
|
338
338
|
<v-card-title class="dialog-title">Make a Call</v-card-title>
|
|
339
339
|
<v-card-text class="dialog-body make-call-body">
|
|
340
340
|
<div class="make-call-field">
|
|
341
|
-
<div class="call-label">
|
|
341
|
+
<div class="call-label">Extension, SIP address, or peer IP</div>
|
|
342
342
|
<v-text-field
|
|
343
343
|
v-model="makeCallForm.target"
|
|
344
|
-
placeholder="
|
|
344
|
+
placeholder="7100 or user@example.com"
|
|
345
345
|
variant="outlined"
|
|
346
346
|
density="compact"
|
|
347
347
|
hide-details="auto"
|
|
@@ -385,9 +385,19 @@
|
|
|
385
385
|
<v-card class="web-phone-dialog" rounded="lg">
|
|
386
386
|
<v-card-title class="dialog-title web-phone-title">
|
|
387
387
|
<span>Web Call</span>
|
|
388
|
-
<
|
|
389
|
-
|
|
390
|
-
|
|
388
|
+
<div class="web-phone-title-actions">
|
|
389
|
+
<v-chip size="small" variant="flat" :class="webPhoneStatusClass">
|
|
390
|
+
{{ webPhoneStatusLabel }}
|
|
391
|
+
</v-chip>
|
|
392
|
+
<v-btn
|
|
393
|
+
icon="mdi-close"
|
|
394
|
+
size="small"
|
|
395
|
+
variant="text"
|
|
396
|
+
title="Close Web Call"
|
|
397
|
+
aria-label="Close Web Call"
|
|
398
|
+
@click="webPhoneDialog = false"
|
|
399
|
+
/>
|
|
400
|
+
</div>
|
|
391
401
|
</v-card-title>
|
|
392
402
|
|
|
393
403
|
<v-card-text class="web-phone-body">
|
|
@@ -547,11 +557,6 @@
|
|
|
547
557
|
</div>
|
|
548
558
|
</v-card-text>
|
|
549
559
|
|
|
550
|
-
<v-card-actions class="dialog-actions pa-0">
|
|
551
|
-
<v-btn class="text-none action-submit full-action" variant="flat" height="44" @click="webPhoneDialog = false">
|
|
552
|
-
Close
|
|
553
|
-
</v-btn>
|
|
554
|
-
</v-card-actions>
|
|
555
560
|
</v-card>
|
|
556
561
|
</v-dialog>
|
|
557
562
|
|
|
@@ -1184,12 +1189,18 @@ function openDialCodeDialog(row: IntercomRow) {
|
|
|
1184
1189
|
dialCodeDialog.value = true;
|
|
1185
1190
|
}
|
|
1186
1191
|
|
|
1187
|
-
function openMakeCallDialog(row: IntercomRow) {
|
|
1192
|
+
async function openMakeCallDialog(row: IntercomRow) {
|
|
1188
1193
|
selectedRow.value = row;
|
|
1189
|
-
makeCallForm.target =
|
|
1194
|
+
makeCallForm.target = sipAccount.value?.extension || "";
|
|
1190
1195
|
makeCallForm.status = "";
|
|
1191
1196
|
makeCallForm.statusType = "info";
|
|
1192
1197
|
makeCallDialog.value = true;
|
|
1198
|
+
if (!sipAccount.value && !sipAccountLoading.value) {
|
|
1199
|
+
await loadSipAccount();
|
|
1200
|
+
if (makeCallDialog.value && !makeCallForm.target) {
|
|
1201
|
+
makeCallForm.target = sipAccount.value?.extension || "";
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1193
1204
|
}
|
|
1194
1205
|
|
|
1195
1206
|
function openWebPhoneDialog(row?: IntercomRow) {
|
|
@@ -1397,13 +1408,13 @@ async function runMakeCall() {
|
|
|
1397
1408
|
if (!selectedRow.value) return;
|
|
1398
1409
|
const target = makeCallForm.target.trim();
|
|
1399
1410
|
if (!target) {
|
|
1400
|
-
makeCallForm.status = "Please enter an
|
|
1411
|
+
makeCallForm.status = "Please enter an extension, SIP address, or peer IP.";
|
|
1401
1412
|
makeCallForm.statusType = "warning";
|
|
1402
1413
|
return;
|
|
1403
1414
|
}
|
|
1404
1415
|
|
|
1405
|
-
if (!
|
|
1406
|
-
makeCallForm.status = "
|
|
1416
|
+
if (!isSipCallTarget(target)) {
|
|
1417
|
+
makeCallForm.status = "Enter a valid extension, SIP address, or IPv4 address.";
|
|
1407
1418
|
makeCallForm.statusType = "warning";
|
|
1408
1419
|
return;
|
|
1409
1420
|
}
|
|
@@ -1425,13 +1436,8 @@ async function runMakeCall() {
|
|
|
1425
1436
|
}
|
|
1426
1437
|
}
|
|
1427
1438
|
|
|
1428
|
-
function
|
|
1429
|
-
|
|
1430
|
-
return parts.length === 4 && parts.every((part) => {
|
|
1431
|
-
if (!/^\d{1,3}$/.test(part)) return false;
|
|
1432
|
-
const value = Number(part);
|
|
1433
|
-
return value >= 0 && value <= 255;
|
|
1434
|
-
});
|
|
1439
|
+
function isSipCallTarget(value = "") {
|
|
1440
|
+
return /^(?:sip:)?[A-Za-z0-9_.+-]+(?:@[A-Za-z0-9.-]+(?::\d{1,5})?)?$/.test(value.trim());
|
|
1435
1441
|
}
|
|
1436
1442
|
|
|
1437
1443
|
async function runHangupCall() {
|
|
@@ -2273,19 +2279,29 @@ export default {
|
|
|
2273
2279
|
}
|
|
2274
2280
|
|
|
2275
2281
|
.web-phone-dialog {
|
|
2282
|
+
display: flex;
|
|
2276
2283
|
width: 820px;
|
|
2284
|
+
max-height: min(90vh, 760px);
|
|
2277
2285
|
max-width: 94vw;
|
|
2278
2286
|
overflow: hidden;
|
|
2279
2287
|
background: #fff;
|
|
2288
|
+
flex-direction: column;
|
|
2280
2289
|
}
|
|
2281
2290
|
|
|
2282
2291
|
.web-phone-title {
|
|
2292
|
+
flex: 0 0 auto;
|
|
2283
2293
|
display: flex;
|
|
2284
2294
|
align-items: center;
|
|
2285
2295
|
justify-content: space-between;
|
|
2286
2296
|
gap: 16px;
|
|
2287
2297
|
}
|
|
2288
2298
|
|
|
2299
|
+
.web-phone-title-actions {
|
|
2300
|
+
display: flex;
|
|
2301
|
+
align-items: center;
|
|
2302
|
+
gap: 8px;
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2289
2305
|
.web-phone-status {
|
|
2290
2306
|
min-width: 92px;
|
|
2291
2307
|
justify-content: center;
|
|
@@ -2317,6 +2333,7 @@ export default {
|
|
|
2317
2333
|
grid-template-columns: minmax(280px, 0.9fr) minmax(320px, 1.1fr);
|
|
2318
2334
|
gap: 24px;
|
|
2319
2335
|
min-height: 440px;
|
|
2336
|
+
overflow: auto;
|
|
2320
2337
|
padding: 22px 24px 24px;
|
|
2321
2338
|
}
|
|
2322
2339
|
|
|
@@ -2668,8 +2685,7 @@ export default {
|
|
|
2668
2685
|
|
|
2669
2686
|
.web-phone-body {
|
|
2670
2687
|
grid-template-columns: minmax(0, 1fr);
|
|
2671
|
-
|
|
2672
|
-
overflow: auto;
|
|
2688
|
+
min-height: 0;
|
|
2673
2689
|
}
|
|
2674
2690
|
|
|
2675
2691
|
.web-phone-settings {
|
|
@@ -583,7 +583,7 @@ async function loadConfig() {
|
|
|
583
583
|
enabled: Boolean(config.enabled),
|
|
584
584
|
onlineMode: Boolean(config.onlineMode),
|
|
585
585
|
readerId: String(config.readerId || readers.value[0]?._id || ""),
|
|
586
|
-
qrFormat:
|
|
586
|
+
qrFormat: "0",
|
|
587
587
|
identificationMethods: {
|
|
588
588
|
facial: configuredMethods?.facial ?? true,
|
|
589
589
|
card: configuredMethods?.card ?? true,
|
|
@@ -750,13 +750,20 @@ async function loadReaderConfiguration() {
|
|
|
750
750
|
try {
|
|
751
751
|
const response = await getReaderConfiguration(form.readerId, {
|
|
752
752
|
general: ["online"],
|
|
753
|
+
identifier: ["pin_identification_enabled", "multi_factor_authentication"],
|
|
753
754
|
});
|
|
754
755
|
const configuration = getConfigurationData(response);
|
|
755
756
|
const general = isUnknownRecord(configuration.general) ? configuration.general : {};
|
|
757
|
+
const identifier = isUnknownRecord(configuration.identifier) ? configuration.identifier : {};
|
|
756
758
|
|
|
757
759
|
if (general.online !== undefined) {
|
|
758
760
|
form.onlineMode = isEnabledConfiguration(general.online);
|
|
759
761
|
}
|
|
762
|
+
if (identifier.pin_identification_enabled !== undefined) {
|
|
763
|
+
const pinEnabled = isEnabledConfiguration(identifier.pin_identification_enabled);
|
|
764
|
+
form.identificationMethods.pin = pinEnabled;
|
|
765
|
+
form.identificationMethods.idPassword = !pinEnabled;
|
|
766
|
+
}
|
|
760
767
|
} catch (error: unknown) {
|
|
761
768
|
console.warn("Unable to load HID reader configuration:", errorConverter(error));
|
|
762
769
|
}
|
|
@@ -769,7 +776,8 @@ function buildPayload() {
|
|
|
769
776
|
enabled: form.identificationMethods.qrCode,
|
|
770
777
|
onlineMode: form.onlineMode,
|
|
771
778
|
readerId: form.readerId,
|
|
772
|
-
|
|
779
|
+
// Visitor QR passes always use Amico alphanumeric qrcodes.
|
|
780
|
+
qrFormat: "0",
|
|
773
781
|
identificationMethods: { ...form.identificationMethods },
|
|
774
782
|
printer: {
|
|
775
783
|
vendorId: form.printer.vendorId.trim(),
|
|
@@ -827,6 +835,20 @@ async function saveIdentificationMethod(method: HidIdentificationMethod) {
|
|
|
827
835
|
|
|
828
836
|
savingSection.value = `method-${method}`;
|
|
829
837
|
try {
|
|
838
|
+
if (method === "pin" || method === "idPassword") {
|
|
839
|
+
if (method === "pin" && form.identificationMethods.pin) {
|
|
840
|
+
form.identificationMethods.idPassword = false;
|
|
841
|
+
} else if (method === "idPassword" && form.identificationMethods.idPassword) {
|
|
842
|
+
form.identificationMethods.pin = false;
|
|
843
|
+
}
|
|
844
|
+
await setReaderConfiguration(form.readerId, {
|
|
845
|
+
identifier: {
|
|
846
|
+
pin_identification_enabled: form.identificationMethods.pin ? "1" : "0",
|
|
847
|
+
// Mode 1: PIN is an alternative credential, never a second factor.
|
|
848
|
+
multi_factor_authentication: "0",
|
|
849
|
+
},
|
|
850
|
+
});
|
|
851
|
+
}
|
|
830
852
|
await updateSitebyId(props.site, buildPayload());
|
|
831
853
|
showMessage(`${identificationMethodOptions.find((item) => item.key === method)?.label || "Identification method"} updated.`, "success");
|
|
832
854
|
} catch (error: unknown) {
|
|
@@ -238,28 +238,28 @@
|
|
|
238
238
|
class="mb-3"
|
|
239
239
|
/>
|
|
240
240
|
|
|
241
|
-
<div class="field-label">
|
|
241
|
+
<div class="field-label">Access PIN (optional)</div>
|
|
242
242
|
<v-text-field
|
|
243
|
-
v-model="form.
|
|
244
|
-
aria-label="
|
|
245
|
-
placeholder="Enter
|
|
243
|
+
v-model="form.accessPin"
|
|
244
|
+
aria-label="Access PIN"
|
|
245
|
+
placeholder="Enter numeric access PIN"
|
|
246
246
|
density="compact"
|
|
247
247
|
variant="outlined"
|
|
248
|
-
|
|
248
|
+
inputmode="numeric"
|
|
249
|
+
autocomplete="new-password"
|
|
250
|
+
:type="showPin ? 'text' : 'password'"
|
|
251
|
+
:append-inner-icon="showPin ? 'mdi-eye-off' : 'mdi-eye'"
|
|
252
|
+
:hint="selectedUser && pinEnrolled ? 'Leave blank to keep the current PIN.' : 'PIN works as an alternative to face or QR.'"
|
|
253
|
+
persistent-hint
|
|
254
|
+
@click:append-inner="showPin = !showPin"
|
|
249
255
|
class="mb-3"
|
|
250
256
|
/>
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
aria-label="Panic Password"
|
|
256
|
-
placeholder="Enter password"
|
|
257
|
+
<v-checkbox
|
|
258
|
+
v-if="selectedUser && pinEnrolled"
|
|
259
|
+
v-model="removePinRequested"
|
|
260
|
+
label="Remove current access PIN"
|
|
257
261
|
density="compact"
|
|
258
|
-
|
|
259
|
-
hide-details="auto"
|
|
260
|
-
:type="showPassword ? 'text' : 'password'"
|
|
261
|
-
:append-inner-icon="showPassword ? 'mdi-eye-off' : 'mdi-eye'"
|
|
262
|
-
@click:append-inner="showPassword = !showPassword"
|
|
262
|
+
hide-details
|
|
263
263
|
/>
|
|
264
264
|
</v-card-text>
|
|
265
265
|
|
|
@@ -306,12 +306,8 @@
|
|
|
306
306
|
><strong>{{
|
|
307
307
|
selectedUser ? getMappingStatus(selectedUser) : "N/A"
|
|
308
308
|
}}</strong>
|
|
309
|
-
<span>
|
|
310
|
-
><strong>{{ selectedUser?.
|
|
311
|
-
<span>Private Password</span
|
|
312
|
-
><strong>{{
|
|
313
|
-
selectedUser?.metadata?.pinPassword ? "********" : "N/A"
|
|
314
|
-
}}</strong>
|
|
309
|
+
<span>Access PIN</span
|
|
310
|
+
><strong>{{ selectedUser?.metadata?.pinEnrolled ? "Configured" : "N/A" }}</strong>
|
|
315
311
|
</div>
|
|
316
312
|
</v-card-text>
|
|
317
313
|
<v-card-actions>
|
|
@@ -377,6 +373,9 @@ const {
|
|
|
377
373
|
getUserImage,
|
|
378
374
|
setUserImage,
|
|
379
375
|
deleteUserImage,
|
|
376
|
+
getUserPinStatus,
|
|
377
|
+
setUserPin,
|
|
378
|
+
deleteUserPin,
|
|
380
379
|
runObjectOperation,
|
|
381
380
|
createIdentity,
|
|
382
381
|
updateIdentity,
|
|
@@ -401,11 +400,13 @@ const formDialog = ref(false);
|
|
|
401
400
|
const viewDialog = ref(false);
|
|
402
401
|
const deleteDialog = ref(false);
|
|
403
402
|
const selectedUser = ref<Record<string, any> | null>(null);
|
|
404
|
-
const
|
|
403
|
+
const showPin = ref(false);
|
|
404
|
+
const pinEnrolled = ref(false);
|
|
405
405
|
const photoInput = ref<HTMLInputElement | null>(null);
|
|
406
406
|
const cameraDialog = ref(false);
|
|
407
407
|
const selectedPhotoFile = ref<File | null>(null);
|
|
408
408
|
const removePhotoRequested = ref(false);
|
|
409
|
+
const removePinRequested = ref(false);
|
|
409
410
|
const snackbar = reactive({
|
|
410
411
|
show: false,
|
|
411
412
|
color: "success",
|
|
@@ -420,8 +421,7 @@ const form = reactive({
|
|
|
420
421
|
name: "",
|
|
421
422
|
hidUserId: "",
|
|
422
423
|
registration: "",
|
|
423
|
-
|
|
424
|
-
pinPassword: "",
|
|
424
|
+
accessPin: "",
|
|
425
425
|
photoPreview: "",
|
|
426
426
|
});
|
|
427
427
|
|
|
@@ -554,8 +554,10 @@ function resetForm() {
|
|
|
554
554
|
form.name = "";
|
|
555
555
|
form.hidUserId = "";
|
|
556
556
|
form.registration = "";
|
|
557
|
-
form.
|
|
558
|
-
|
|
557
|
+
form.accessPin = "";
|
|
558
|
+
pinEnrolled.value = false;
|
|
559
|
+
showPin.value = false;
|
|
560
|
+
removePinRequested.value = false;
|
|
559
561
|
form.photoPreview = "";
|
|
560
562
|
selectedPhotoFile.value = null;
|
|
561
563
|
removePhotoRequested.value = false;
|
|
@@ -582,13 +584,24 @@ async function openEdit(user: Record<string, any>) {
|
|
|
582
584
|
form.name = getName(user);
|
|
583
585
|
form.hidUserId = formatHidUid(user.hidUserId);
|
|
584
586
|
form.registration = user.registration ?? "";
|
|
585
|
-
form.
|
|
586
|
-
|
|
587
|
+
form.accessPin = "";
|
|
588
|
+
pinEnrolled.value = Boolean(user.metadata?.pinEnrolled);
|
|
589
|
+
removePinRequested.value = false;
|
|
587
590
|
form.photoPreview = getUserImageSrc(user);
|
|
588
591
|
selectedPhotoFile.value = null;
|
|
589
592
|
removePhotoRequested.value = false;
|
|
590
593
|
formDialog.value = true;
|
|
591
594
|
|
|
595
|
+
try {
|
|
596
|
+
const status = await getUserPinStatus(
|
|
597
|
+
form.reader,
|
|
598
|
+
toHidNumericId(user.hidUserId),
|
|
599
|
+
);
|
|
600
|
+
pinEnrolled.value = Boolean(status?.data?.pinEnrolled);
|
|
601
|
+
} catch {
|
|
602
|
+
// Preserve the last known status if the reader is temporarily offline.
|
|
603
|
+
}
|
|
604
|
+
|
|
592
605
|
if (!form.photoPreview) {
|
|
593
606
|
await loadUserImages([user]);
|
|
594
607
|
if (selectedUser.value?._rowKey === user._rowKey) {
|
|
@@ -672,6 +685,10 @@ async function saveUser() {
|
|
|
672
685
|
showToast("Please complete the required HID user fields.", "error");
|
|
673
686
|
return;
|
|
674
687
|
}
|
|
688
|
+
if (form.accessPin && !/^\d{1,32}$/.test(form.accessPin)) {
|
|
689
|
+
showToast("Access PIN must contain numbers only.", "error");
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
675
692
|
|
|
676
693
|
saving.value = true;
|
|
677
694
|
try {
|
|
@@ -688,7 +705,6 @@ async function saveUser() {
|
|
|
688
705
|
const payload = {
|
|
689
706
|
hidUserId: String(hidUser.id),
|
|
690
707
|
registration: form.registration,
|
|
691
|
-
cardNo: form.cardNo,
|
|
692
708
|
type: props.identityType as
|
|
693
709
|
| "resident"
|
|
694
710
|
| "staff"
|
|
@@ -707,7 +723,7 @@ async function saveUser() {
|
|
|
707
723
|
photo: form.photoPreview,
|
|
708
724
|
imageTimestamp: selectedUser.value?.metadata?.imageTimestamp || "",
|
|
709
725
|
facialScores: selectedUser.value?.metadata?.facialScores || {},
|
|
710
|
-
|
|
726
|
+
pinEnrolled: pinEnrolled.value,
|
|
711
727
|
},
|
|
712
728
|
};
|
|
713
729
|
|
|
@@ -716,6 +732,7 @@ async function saveUser() {
|
|
|
716
732
|
await syncHidUserRole(readerId, hidUser.id, isAdministratorIdentity());
|
|
717
733
|
const facialResult = await syncFacialImage(readerId, hidUser.id);
|
|
718
734
|
applyFacialResult(payload.metadata, facialResult);
|
|
735
|
+
payload.metadata.pinEnrolled = await syncAccessPin(readerId, hidUser.id);
|
|
719
736
|
if (selectedUser.value._id) {
|
|
720
737
|
await updateIdentity(selectedUser.value._id, payload);
|
|
721
738
|
} else {
|
|
@@ -726,6 +743,7 @@ async function saveUser() {
|
|
|
726
743
|
await syncHidUserRole(readerId, hidUser.id, isAdministratorIdentity());
|
|
727
744
|
const facialResult = await syncFacialImage(readerId, hidUser.id);
|
|
728
745
|
applyFacialResult(payload.metadata, facialResult);
|
|
746
|
+
payload.metadata.pinEnrolled = await syncAccessPin(readerId, hidUser.id);
|
|
729
747
|
await saveIdentityOnReader(readerId, payload);
|
|
730
748
|
selectedReaderId.value = readerId;
|
|
731
749
|
}
|
|
@@ -745,6 +763,21 @@ async function saveUser() {
|
|
|
745
763
|
}
|
|
746
764
|
}
|
|
747
765
|
|
|
766
|
+
async function syncAccessPin(readerId: string, hidUserId: number) {
|
|
767
|
+
if (removePinRequested.value) {
|
|
768
|
+
await deleteUserPin(readerId, hidUserId);
|
|
769
|
+
pinEnrolled.value = false;
|
|
770
|
+
return false;
|
|
771
|
+
}
|
|
772
|
+
if (form.accessPin) {
|
|
773
|
+
await setUserPin(readerId, hidUserId, form.accessPin);
|
|
774
|
+
form.accessPin = "";
|
|
775
|
+
pinEnrolled.value = true;
|
|
776
|
+
return true;
|
|
777
|
+
}
|
|
778
|
+
return pinEnrolled.value;
|
|
779
|
+
}
|
|
780
|
+
|
|
748
781
|
type FacialSyncResult = {
|
|
749
782
|
action: "none" | "enrolled" | "removed";
|
|
750
783
|
timestamp?: number;
|
|
@@ -806,6 +839,10 @@ async function deleteUser() {
|
|
|
806
839
|
toHidNumericId(selectedUser.value.hidUserId),
|
|
807
840
|
false
|
|
808
841
|
);
|
|
842
|
+
await deleteUserPin(
|
|
843
|
+
selectedReaderId.value,
|
|
844
|
+
toHidNumericId(selectedUser.value.hidUserId),
|
|
845
|
+
);
|
|
809
846
|
await deleteHidUserFromReader(selectedReaderId.value, selectedUser.value);
|
|
810
847
|
if (selectedUser.value._id) {
|
|
811
848
|
await deleteIdentity(selectedUser.value._id);
|
|
@@ -3,10 +3,8 @@
|
|
|
3
3
|
<v-col cols="12" class="mb-2">
|
|
4
4
|
<v-row no-gutters>
|
|
5
5
|
<v-btn
|
|
6
|
-
class="text-none mr-2"
|
|
7
|
-
|
|
8
|
-
variant="tonal"
|
|
9
|
-
size="large"
|
|
6
|
+
class="text-none mr-2 screen-btn-primary"
|
|
7
|
+
variant="flat"
|
|
10
8
|
@click="inviteMember()"
|
|
11
9
|
v-if="props.inviteMember"
|
|
12
10
|
>
|
|
@@ -15,8 +13,12 @@
|
|
|
15
13
|
</v-row>
|
|
16
14
|
</v-col>
|
|
17
15
|
<v-col cols="12">
|
|
18
|
-
<v-card width="100%" variant="
|
|
19
|
-
<v-toolbar
|
|
16
|
+
<v-card width="100%" variant="flat" class="table-card">
|
|
17
|
+
<v-toolbar
|
|
18
|
+
density="compact"
|
|
19
|
+
color="transparent"
|
|
20
|
+
class="table-card__toolbar"
|
|
21
|
+
>
|
|
20
22
|
<template #prepend>
|
|
21
23
|
<v-btn
|
|
22
24
|
fab
|
|
@@ -30,7 +32,7 @@
|
|
|
30
32
|
|
|
31
33
|
<template #append>
|
|
32
34
|
<v-row no-gutters justify="end" align="center">
|
|
33
|
-
<span class="mr-2
|
|
35
|
+
<span class="mr-2 table-card__range">
|
|
34
36
|
{{ pageRange }}
|
|
35
37
|
</span>
|
|
36
38
|
<local-pagination
|
|
@@ -77,7 +79,7 @@
|
|
|
77
79
|
<span class="text-caption font-weight-bold text-capitalize">
|
|
78
80
|
permissions
|
|
79
81
|
</span>
|
|
80
|
-
<
|
|
82
|
+
<StatusChip :dot="false" tone="info" :label="String(value.length)" />
|
|
81
83
|
</template>
|
|
82
84
|
<template #item.createdAt="{ item }">
|
|
83
85
|
{{ item.createdAt ? formatDate(item.createdAt) : "" }}
|
|
@@ -4,9 +4,8 @@
|
|
|
4
4
|
<v-row no-gutters>
|
|
5
5
|
<v-btn
|
|
6
6
|
v-if="props.seatManagement !== 'index'"
|
|
7
|
-
class="text-none"
|
|
8
|
-
|
|
9
|
-
variant="tonal"
|
|
7
|
+
class="text-none screen-btn-primary"
|
|
8
|
+
variant="flat"
|
|
10
9
|
:to="{
|
|
11
10
|
name: props.seatManagement,
|
|
12
11
|
params: { organization: props.orgId },
|
|
@@ -19,8 +18,12 @@
|
|
|
19
18
|
</v-col>
|
|
20
19
|
|
|
21
20
|
<v-col cols="12">
|
|
22
|
-
<v-card width="100%" variant="
|
|
23
|
-
<v-toolbar
|
|
21
|
+
<v-card width="100%" variant="flat" class="table-card">
|
|
22
|
+
<v-toolbar
|
|
23
|
+
density="compact"
|
|
24
|
+
color="transparent"
|
|
25
|
+
class="table-card__toolbar"
|
|
26
|
+
>
|
|
24
27
|
<template #prepend>
|
|
25
28
|
<v-btn fab icon density="comfortable" @click="getAll()">
|
|
26
29
|
<v-icon>mdi-refresh</v-icon>
|
|
@@ -29,7 +32,7 @@
|
|
|
29
32
|
|
|
30
33
|
<template #append>
|
|
31
34
|
<v-row no-gutters justify="end" align="center">
|
|
32
|
-
<span class="mr-2
|
|
35
|
+
<span class="mr-2 table-card__range">
|
|
33
36
|
{{ pageRange }}
|
|
34
37
|
</span>
|
|
35
38
|
<local-pagination
|
|
@@ -17,10 +17,8 @@
|
|
|
17
17
|
<v-row no-gutters align="center" class="w-100">
|
|
18
18
|
<v-col cols="auto" v-if="canCheckInOut">
|
|
19
19
|
<v-btn
|
|
20
|
-
class="text-none"
|
|
21
|
-
|
|
22
|
-
variant="tonal"
|
|
23
|
-
size="large"
|
|
20
|
+
class="text-none screen-btn-primary"
|
|
21
|
+
variant="flat"
|
|
24
22
|
@click="onCreateItem"
|
|
25
23
|
>
|
|
26
24
|
{{ hasCheckedInToday ? "Check Out" : "Check In" }}
|
|
@@ -28,7 +26,7 @@
|
|
|
28
26
|
</v-col>
|
|
29
27
|
<v-spacer />
|
|
30
28
|
|
|
31
|
-
<v-col cols="auto">
|
|
29
|
+
<v-col cols="auto" class="filter-field">
|
|
32
30
|
<v-text-field
|
|
33
31
|
v-model="searchInput"
|
|
34
32
|
density="compact"
|
|
@@ -3,11 +3,9 @@
|
|
|
3
3
|
<v-col cols="12" class="mb-2">
|
|
4
4
|
<v-row no-gutters>
|
|
5
5
|
<v-btn
|
|
6
|
-
class="text-none"
|
|
7
|
-
|
|
8
|
-
variant="tonal"
|
|
6
|
+
class="text-none screen-btn-primary"
|
|
7
|
+
variant="flat"
|
|
9
8
|
@click="createDialog = true"
|
|
10
|
-
size="large"
|
|
11
9
|
v-if="canCreateRole"
|
|
12
10
|
>
|
|
13
11
|
Create role
|
|
@@ -17,12 +15,15 @@
|
|
|
17
15
|
<v-col cols="12">
|
|
18
16
|
<v-card
|
|
19
17
|
width="100%"
|
|
20
|
-
variant="
|
|
21
|
-
|
|
22
|
-
rounded="lg"
|
|
18
|
+
variant="flat"
|
|
19
|
+
class="table-card"
|
|
23
20
|
:loading="loading"
|
|
24
21
|
>
|
|
25
|
-
<v-toolbar
|
|
22
|
+
<v-toolbar
|
|
23
|
+
density="compact"
|
|
24
|
+
color="transparent"
|
|
25
|
+
class="table-card__toolbar"
|
|
26
|
+
>
|
|
26
27
|
<template #prepend>
|
|
27
28
|
<v-btn fab icon density="comfortable" @click="getRoles()">
|
|
28
29
|
<v-icon>mdi-refresh</v-icon>
|
|
@@ -31,7 +32,7 @@
|
|
|
31
32
|
|
|
32
33
|
<template #append>
|
|
33
34
|
<v-row no-gutters justify="end" align="center">
|
|
34
|
-
<span class="mr-2
|
|
35
|
+
<span class="mr-2 table-card__range">
|
|
35
36
|
{{ pageRange }}
|
|
36
37
|
</span>
|
|
37
38
|
<local-pagination
|
|
@@ -58,7 +59,11 @@
|
|
|
58
59
|
<span class="text-caption font-weight-bold text-capitalize">
|
|
59
60
|
permissions
|
|
60
61
|
</span>
|
|
61
|
-
<
|
|
62
|
+
<StatusChip
|
|
63
|
+
:dot="false"
|
|
64
|
+
tone="info"
|
|
65
|
+
:label="String(getPermissionCount(value))"
|
|
66
|
+
/>
|
|
62
67
|
</template>
|
|
63
68
|
|
|
64
69
|
<template #item.action-table="{ item }" v-if="canDeleteRole">
|
|
@@ -21,10 +21,8 @@
|
|
|
21
21
|
<v-row no-gutters align="center" class="w-100">
|
|
22
22
|
<v-col cols="auto" v-if="canCreateScheduleTask">
|
|
23
23
|
<v-btn
|
|
24
|
-
class="text-none"
|
|
25
|
-
|
|
26
|
-
variant="tonal"
|
|
27
|
-
size="large"
|
|
24
|
+
class="text-none screen-btn-primary"
|
|
25
|
+
variant="flat"
|
|
28
26
|
@click="openFormDialog"
|
|
29
27
|
>
|
|
30
28
|
Create Schedule Task
|
|
@@ -33,7 +31,7 @@
|
|
|
33
31
|
|
|
34
32
|
<v-spacer />
|
|
35
33
|
|
|
36
|
-
<v-col cols="auto">
|
|
34
|
+
<v-col cols="auto" class="filter-field">
|
|
37
35
|
<v-text-field
|
|
38
36
|
v-model="searchInput"
|
|
39
37
|
density="compact"
|
|
@@ -49,28 +47,31 @@
|
|
|
49
47
|
|
|
50
48
|
<template #item.areas="{ value }">
|
|
51
49
|
<div v-if="value && value.length > 0">
|
|
52
|
-
<
|
|
50
|
+
<StatusChip
|
|
53
51
|
v-for="(area, idx) in value.slice(0, 2)"
|
|
54
52
|
:key="idx"
|
|
55
|
-
size="small"
|
|
56
53
|
class="mr-1 mb-1"
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
54
|
+
:dot="false"
|
|
55
|
+
tone="neutral"
|
|
56
|
+
:label="area.name"
|
|
57
|
+
/>
|
|
58
|
+
<StatusChip
|
|
59
|
+
v-if="value.length > 2"
|
|
60
|
+
class="mb-1"
|
|
61
|
+
:dot="false"
|
|
62
|
+
tone="neutral"
|
|
63
|
+
:label="`+${value.length - 2} more`"
|
|
64
|
+
/>
|
|
63
65
|
</div>
|
|
64
66
|
<span v-else>N/A</span>
|
|
65
67
|
</template>
|
|
66
68
|
|
|
67
69
|
<template #item.status="{ value }">
|
|
68
|
-
<
|
|
69
|
-
:color="value === 'active' ? 'success' : 'default'"
|
|
70
|
+
<StatusChip
|
|
70
71
|
class="text-capitalize"
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
:status="value"
|
|
73
|
+
:label="value || 'N/A'"
|
|
74
|
+
/>
|
|
74
75
|
</template>
|
|
75
76
|
</TableMain>
|
|
76
77
|
|
|
@@ -270,15 +271,11 @@
|
|
|
270
271
|
Status:
|
|
271
272
|
</v-col>
|
|
272
273
|
<v-col cols="7" class="text-subtitle-2">
|
|
273
|
-
<
|
|
274
|
-
:color="
|
|
275
|
-
selectedTask.status === 'active' ? 'success' : 'default'
|
|
276
|
-
"
|
|
277
|
-
size="small"
|
|
274
|
+
<StatusChip
|
|
278
275
|
class="text-capitalize"
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
276
|
+
:status="selectedTask.status"
|
|
277
|
+
:label="selectedTask.status || 'N/A'"
|
|
278
|
+
/>
|
|
282
279
|
</v-col>
|
|
283
280
|
</v-row>
|
|
284
281
|
|