@7365admin1/layer-common 4.2.8 → 4.2.9-staging.278
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/components/VisitorManagement.vue +231 -81
- package/package.json +1 -1
|
@@ -632,116 +632,187 @@
|
|
|
632
632
|
edit-button-label="Edit Visitor"
|
|
633
633
|
>
|
|
634
634
|
<template v-slot:content>
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
635
|
+
<!--
|
|
636
|
+
THE PREVIEW'S FIELD LIST.
|
|
637
|
+
|
|
638
|
+
Was `<strong>Label:</strong> value` inside a gapless `v-row` - a
|
|
639
|
+
bold browser-default label beside an unstyled value, neither of
|
|
640
|
+
them on the type scale, and every row flush against the next.
|
|
641
|
+
|
|
642
|
+
The label stays to the LEFT of its value, which is the shape this
|
|
643
|
+
dialog has always had and the one the owner picked on 2026-09-03
|
|
644
|
+
over the stacked `AccessCardDetailsDialog` treatment. What changes
|
|
645
|
+
is that the pair is now on the type scale, in a fixed label column
|
|
646
|
+
so every value starts on the same edge, with the card's own
|
|
647
|
+
`--border` hairline separating one row from the next. The type
|
|
648
|
+
tokens are `AccessCardDetailsDialog`'s; only the arrangement is
|
|
649
|
+
this dialog's own.
|
|
650
|
+
-->
|
|
651
|
+
<div class="visitor-preview">
|
|
652
|
+
<!--
|
|
653
|
+
The status the Guests tab decides on. It was not shown here at
|
|
654
|
+
all, which is why a disabled Check In button had nothing on the
|
|
655
|
+
card explaining itself. Gated on the same `activeTab === 'guests'
|
|
656
|
+
&& item?.status` the row's own status column uses, so every other
|
|
657
|
+
tab's preview keeps exactly the fields it had.
|
|
658
|
+
-->
|
|
659
|
+
<div
|
|
660
|
+
v-if="activeTab === 'guests' && selectedVisitorStatus"
|
|
661
|
+
class="visitor-preview__row visitor-preview__row--tall"
|
|
662
|
+
>
|
|
663
|
+
<div class="visitor-preview__label">Status</div>
|
|
664
|
+
<div>
|
|
665
|
+
<StatusChip
|
|
666
|
+
class="text-capitalize"
|
|
667
|
+
:status="selectedVisitorStatus"
|
|
668
|
+
:label="getVisitorStatusLabel(selectedVisitorStatus)"
|
|
669
|
+
/>
|
|
670
|
+
</div>
|
|
671
|
+
</div>
|
|
672
|
+
|
|
673
|
+
<!--
|
|
674
|
+
`<template>` and not a wrapping `<div>`: every row draws its own
|
|
675
|
+
hairline, so a wrapper that renders empty - which is every field
|
|
676
|
+
this visitor type carries no value for - would put a rule across
|
|
677
|
+
nothing. Only the branch that matches becomes a row, which is
|
|
678
|
+
also what makes `:last-child` land on the real last row.
|
|
679
|
+
-->
|
|
680
|
+
<template v-for="(label, key) in formattedFields" :key="key">
|
|
681
|
+
<div
|
|
638
682
|
v-if="
|
|
639
683
|
key === 'checkIn' &&
|
|
640
684
|
!selectedVisitorObject[key] &&
|
|
641
685
|
canUpdateVisitor
|
|
642
686
|
"
|
|
643
|
-
class="
|
|
687
|
+
class="visitor-preview__row visitor-preview__row--tall"
|
|
644
688
|
>
|
|
645
|
-
<
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
689
|
+
<div class="visitor-preview__label">{{ label }}</div>
|
|
690
|
+
<div>
|
|
691
|
+
<!-- The design's check-in / check-out pair. Was Vuetify's
|
|
692
|
+
`color="success"` beside a hardcoded `color="red"` - two
|
|
693
|
+
different systems on one field list, and neither of them
|
|
694
|
+
a token. `:loading` is dropped rather than re-invented:
|
|
695
|
+
the same flag already drives `:disabled`, which is the
|
|
696
|
+
precedent set when the scanned-visitor form converted. -->
|
|
697
|
+
<!-- A guest whose registration is still awaiting a decision
|
|
698
|
+
must not be checked in from here. The Guests row already
|
|
699
|
+
withholds its own Checkin button while `status` is
|
|
700
|
+
"pending" (DV-0080); this preview is the same action on
|
|
701
|
+
the same record, so it is gated too - disabled rather
|
|
702
|
+
than hidden, so the Check In field itself still reads.
|
|
703
|
+
The Status row above is what says why. -->
|
|
704
|
+
<AppButton
|
|
705
|
+
variant="ok"
|
|
706
|
+
icon="mdi-check"
|
|
707
|
+
:disabled="
|
|
708
|
+
isVisitorDataFromScannedQRCodeCheckingInOut ||
|
|
709
|
+
isSelectedVisitorPendingApproval
|
|
710
|
+
"
|
|
711
|
+
@click="handleCheckin(selectedVisitorDataObject)"
|
|
712
|
+
>
|
|
713
|
+
Check In
|
|
714
|
+
</AppButton>
|
|
715
|
+
</div>
|
|
716
|
+
</div>
|
|
717
|
+
|
|
718
|
+
<div
|
|
719
|
+
v-else-if="
|
|
664
720
|
key === 'checkOut' &&
|
|
665
721
|
selectedVisitorObject['checkIn'] &&
|
|
666
722
|
!selectedVisitorObject[key] &&
|
|
667
723
|
canCheckoutVisitor
|
|
668
724
|
"
|
|
669
|
-
class="
|
|
725
|
+
class="visitor-preview__row visitor-preview__row--tall"
|
|
670
726
|
>
|
|
671
|
-
<
|
|
672
|
-
<
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
727
|
+
<div class="visitor-preview__label">{{ label }}</div>
|
|
728
|
+
<div>
|
|
729
|
+
<AppButton
|
|
730
|
+
variant="err"
|
|
731
|
+
icon="mdi-close"
|
|
732
|
+
:disabled="loading.checkingOut"
|
|
733
|
+
@click="handleCheckout(selectedVisitorId as string)"
|
|
734
|
+
>
|
|
735
|
+
Check Out
|
|
736
|
+
</AppButton>
|
|
737
|
+
</div>
|
|
738
|
+
</div>
|
|
682
739
|
|
|
683
|
-
<
|
|
740
|
+
<div
|
|
684
741
|
v-else-if="key === 'nric' && selectedVisitorObject['nric']"
|
|
685
|
-
class="
|
|
742
|
+
class="visitor-preview__row"
|
|
686
743
|
>
|
|
687
|
-
<
|
|
688
|
-
<
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
744
|
+
<div class="visitor-preview__label">{{ label }}</div>
|
|
745
|
+
<div
|
|
746
|
+
v-tooltip:top="selectedVisitorObject[key]"
|
|
747
|
+
class="visitor-preview__value"
|
|
748
|
+
>
|
|
749
|
+
{{ maskNRIC(selectedVisitorObject[key]) }}
|
|
750
|
+
</div>
|
|
751
|
+
</div>
|
|
752
|
+
|
|
753
|
+
<div
|
|
693
754
|
v-else-if="
|
|
694
755
|
key === 'attachments' &&
|
|
695
756
|
selectedVisitorObject?.[key]?.length > 0
|
|
696
757
|
"
|
|
697
|
-
class="
|
|
758
|
+
class="visitor-preview__row visitor-preview__row--tall"
|
|
698
759
|
>
|
|
699
|
-
<
|
|
760
|
+
<div class="visitor-preview__label">{{ label }}</div>
|
|
700
761
|
<div class="d-flex flex-wrap ga-2">
|
|
701
762
|
<AttachmentsViewer
|
|
702
|
-
:files="
|
|
703
|
-
mappedAttachments(selectedVisitorObject.attachments)
|
|
704
|
-
"
|
|
763
|
+
:files="mappedAttachments(selectedVisitorObject.attachments)"
|
|
705
764
|
title=""
|
|
706
765
|
show-file-type-title
|
|
707
766
|
/>
|
|
708
767
|
</div>
|
|
709
|
-
</
|
|
710
|
-
|
|
768
|
+
</div>
|
|
769
|
+
|
|
770
|
+
<!--
|
|
771
|
+
Two fixes the redesign is not allowed to leave behind. The
|
|
772
|
+
branch had no value check, so "Entry Image" and "Exit Image"
|
|
773
|
+
both printed as bare labels over an AttachmentsViewer holding
|
|
774
|
+
`[undefined]` on every visitor without snapshots - which is
|
|
775
|
+
every visitor the ANPR cameras never saw. And BOTH keys read
|
|
776
|
+
`snapshotEntryImage`, so a visitor with both snapshots was
|
|
777
|
+
shown the entry photo twice, the second time labelled "Exit
|
|
778
|
+
Image".
|
|
779
|
+
-->
|
|
780
|
+
<div
|
|
711
781
|
v-else-if="
|
|
712
|
-
key === 'snapshotEntryImage' ||
|
|
782
|
+
(key === 'snapshotEntryImage' ||
|
|
783
|
+
key === 'snapshotExitImage') &&
|
|
784
|
+
selectedVisitorObject[key]
|
|
713
785
|
"
|
|
714
|
-
class="
|
|
786
|
+
class="visitor-preview__row visitor-preview__row--tall"
|
|
715
787
|
>
|
|
716
|
-
<
|
|
788
|
+
<div class="visitor-preview__label">{{ label }}</div>
|
|
717
789
|
<div class="d-flex flex-wrap ga-2">
|
|
718
790
|
<AttachmentsViewer
|
|
719
|
-
:files="
|
|
720
|
-
mappedSnapshots([
|
|
721
|
-
selectedVisitorObject.snapshotEntryImage,
|
|
722
|
-
])
|
|
723
|
-
"
|
|
791
|
+
:files="mappedSnapshots([selectedVisitorObject[key]])"
|
|
724
792
|
anpr
|
|
725
793
|
:site="siteId"
|
|
726
794
|
title="Snapshot"
|
|
727
795
|
/>
|
|
728
796
|
</div>
|
|
729
|
-
</
|
|
797
|
+
</div>
|
|
730
798
|
|
|
731
|
-
<
|
|
799
|
+
<div
|
|
732
800
|
v-else-if="selectedVisitorObject[key]"
|
|
733
|
-
class="
|
|
734
|
-
|
|
735
|
-
{{
|
|
736
|
-
<
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
801
|
+
class="visitor-preview__row"
|
|
802
|
+
>
|
|
803
|
+
<div class="visitor-preview__label">{{ label }}</div>
|
|
804
|
+
<div class="visitor-preview__value d-flex align-center ga-2">
|
|
805
|
+
{{ formatValues(key, selectedVisitorObject[key]) }}
|
|
806
|
+
<TooltipInfo
|
|
807
|
+
v-if="key === 'checkOut'"
|
|
808
|
+
text="Manual Checkout"
|
|
809
|
+
density="compact"
|
|
810
|
+
size="x-small"
|
|
811
|
+
/>
|
|
812
|
+
</div>
|
|
813
|
+
</div>
|
|
814
|
+
</template>
|
|
815
|
+
</div>
|
|
745
816
|
</template>
|
|
746
817
|
</VehicleUpdateMoreAction>
|
|
747
818
|
</v-dialog>
|
|
@@ -1816,6 +1887,17 @@ const selectedVisitorDataObject = computed(() => {
|
|
|
1816
1887
|
return items.value.find((x: any) => x?._id === selectedVisitorId.value) || {};
|
|
1817
1888
|
});
|
|
1818
1889
|
|
|
1890
|
+
const selectedVisitorStatus = computed(
|
|
1891
|
+
() => (selectedVisitorDataObject.value?.status as string) ?? ""
|
|
1892
|
+
);
|
|
1893
|
+
|
|
1894
|
+
/** Self-service guests land in the list as "pending" until someone approves
|
|
1895
|
+
or rejects them - check-in is not a decision that should be available while
|
|
1896
|
+
that is still open. */
|
|
1897
|
+
const isSelectedVisitorPendingApproval = computed(
|
|
1898
|
+
() => selectedVisitorStatus.value === "pending"
|
|
1899
|
+
);
|
|
1900
|
+
|
|
1819
1901
|
const formatType = (item: any) =>
|
|
1820
1902
|
(item.deliveryType ? item.deliveryType + "-" : "") + item.type;
|
|
1821
1903
|
|
|
@@ -1952,15 +2034,27 @@ const selectedVisitorObject = computed<Record<string, any>>(() => {
|
|
|
1952
2034
|
];
|
|
1953
2035
|
includedKeys.unshift(...(typeFieldMap[type] ?? []));
|
|
1954
2036
|
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
2037
|
+
/**
|
|
2038
|
+
* V-07: THE DETAIL DIALOG PRINTED `undefined / undefined / 12-34`.
|
|
2039
|
+
*
|
|
2040
|
+
* The guard was an OR over the three parts, but the string interpolated all
|
|
2041
|
+
* THREE unconditionally - so a visitor recorded against a unit with no block
|
|
2042
|
+
* and no level (every walk-in registered from the unit number alone) got the
|
|
2043
|
+
* word `undefined` twice, joined by the separators, in front of a perfectly
|
|
2044
|
+
* good unit. The `else if (block)` branch below it could never run: the OR
|
|
2045
|
+
* above had already matched on the same block.
|
|
2046
|
+
*
|
|
2047
|
+
* Only the parts that EXIST are joined now, which is the same rule
|
|
2048
|
+
* `useSecurityUtils().formatLocation` already applies everywhere else, so a
|
|
2049
|
+
* full location still reads `Block A / Level 12 / 12-34` exactly as before
|
|
2050
|
+
* and a partial one reads `12-34` instead of two undefineds.
|
|
2051
|
+
*/
|
|
2052
|
+
const locationParts = [obj?.block?.name, obj?.level?.name, obj?.unitName]
|
|
2053
|
+
.filter((part) => part !== null && part !== undefined && part !== "");
|
|
2054
|
+
|
|
2055
|
+
const locationString = locationParts.length
|
|
2056
|
+
? locationParts.join(" / ")
|
|
2057
|
+
: "N/A";
|
|
1964
2058
|
|
|
1965
2059
|
return {
|
|
1966
2060
|
...Object.fromEntries(
|
|
@@ -2739,6 +2833,62 @@ watchEffect(async () => {
|
|
|
2739
2833
|
</script>
|
|
2740
2834
|
|
|
2741
2835
|
<style scoped>
|
|
2836
|
+
/*
|
|
2837
|
+
* THE PREVIEW DIALOG'S FIELD LIST - LABEL BESIDE VALUE.
|
|
2838
|
+
*
|
|
2839
|
+
* The type comes from `AccessCardDetailsDialog` unchanged - the label on
|
|
2840
|
+
* `--fs-breadcrumb`/`--text2`, the value on `--fs-cell`/`--fw-cell`/`--text` -
|
|
2841
|
+
* so a field reads the same weight here as it does on an access card. The
|
|
2842
|
+
* arrangement is this dialog's: a FIXED 116px label column, so every value in
|
|
2843
|
+
* the card starts on one edge instead of wherever its own label happened to
|
|
2844
|
+
* end, which is what the old `d-flex ga-3` gave.
|
|
2845
|
+
*
|
|
2846
|
+
* `minmax(0, 1fr)` on the value track and not `1fr`: a grid track's default
|
|
2847
|
+
* `min-width: auto` refuses to shrink below its content, so a long company
|
|
2848
|
+
* name or a `Block / Level / Unit` string would push the card wider than the
|
|
2849
|
+
* 450px the dialog is opened at rather than wrap inside it.
|
|
2850
|
+
*
|
|
2851
|
+
* The hairline is the card's own `--border`, on the row and not between rows,
|
|
2852
|
+
* so it tracks the theme with everything else on the card. The last row drops
|
|
2853
|
+
* it - a rule directly above the footer's own edge reads as a double line.
|
|
2854
|
+
*/
|
|
2855
|
+
.visitor-preview {
|
|
2856
|
+
display: flex;
|
|
2857
|
+
flex-direction: column;
|
|
2858
|
+
padding-bottom: 8px;
|
|
2859
|
+
}
|
|
2860
|
+
|
|
2861
|
+
.visitor-preview__row {
|
|
2862
|
+
display: grid;
|
|
2863
|
+
grid-template-columns: 116px minmax(0, 1fr);
|
|
2864
|
+
gap: 16px;
|
|
2865
|
+
align-items: baseline;
|
|
2866
|
+
padding: 9px 0;
|
|
2867
|
+
border-bottom: 1px solid var(--border);
|
|
2868
|
+
}
|
|
2869
|
+
|
|
2870
|
+
.visitor-preview__row:last-child {
|
|
2871
|
+
border-bottom: none;
|
|
2872
|
+
}
|
|
2873
|
+
|
|
2874
|
+
/* A chip, a 40px button and an attachment tile have no text baseline worth
|
|
2875
|
+
aligning a label to; those rows centre instead. */
|
|
2876
|
+
.visitor-preview__row--tall {
|
|
2877
|
+
align-items: center;
|
|
2878
|
+
}
|
|
2879
|
+
|
|
2880
|
+
.visitor-preview__label {
|
|
2881
|
+
font-size: var(--fs-breadcrumb);
|
|
2882
|
+
color: var(--text2);
|
|
2883
|
+
}
|
|
2884
|
+
|
|
2885
|
+
.visitor-preview__value {
|
|
2886
|
+
font-size: var(--fs-cell);
|
|
2887
|
+
font-weight: var(--fw-cell);
|
|
2888
|
+
color: var(--text);
|
|
2889
|
+
word-break: break-word;
|
|
2890
|
+
}
|
|
2891
|
+
|
|
2742
2892
|
/* The width the type filter carried as `max-width="200"` on the v-select. */
|
|
2743
2893
|
.visitor-type-filter {
|
|
2744
2894
|
max-width: 200px;
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@7365admin1/layer-common",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "4.2.
|
|
5
|
+
"version": "4.2.9-staging.278",
|
|
6
6
|
"author": "7365admin1",
|
|
7
7
|
"main": "./nuxt.config.ts",
|
|
8
8
|
"//files": "What a consumer extending this layer actually loads. Without this npm ships the whole working tree - the changesets, the CI workflows, the render harness in tools/ and any scratch directory that happened to exist at publish time. Nuxt resolves a layer by directory, so every runtime directory below has to stay listed; adding a new top-level runtime directory means adding it here too.",
|