@7365admin1/layer-common 4.2.8 → 4.2.9-staging.277

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.
@@ -632,116 +632,187 @@
632
632
  edit-button-label="Edit Visitor"
633
633
  >
634
634
  <template v-slot:content>
635
- <v-row no-gutters class="mb-4">
636
- <v-col v-for="(label, key) in formattedFields" :key="key" cols="12">
637
- <span
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="d-flex align-center"
687
+ class="visitor-preview__row visitor-preview__row--tall"
644
688
  >
645
- <strong>{{ label }}:</strong>
646
- <!-- The design's check-in / check-out pair. Was Vuetify's
647
- `color="success"` beside a hardcoded `color="red"` - two
648
- different systems on one field list, and neither of them a
649
- token. `:loading` is dropped rather than re-invented: the
650
- same flag already drives `:disabled`, which is the
651
- precedent set when the scanned-visitor form converted. -->
652
- <AppButton
653
- variant="ok"
654
- icon="mdi-check"
655
- class="ml-3"
656
- :disabled="isVisitorDataFromScannedQRCodeCheckingInOut"
657
- @click="handleCheckin(selectedVisitorDataObject)"
658
- >
659
- Check In
660
- </AppButton>
661
- </span>
662
- <span
663
- v-if="
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="d-flex align-center"
725
+ class="visitor-preview__row visitor-preview__row--tall"
670
726
  >
671
- <strong>{{ label }}:</strong>
672
- <AppButton
673
- variant="err"
674
- icon="mdi-close"
675
- class="ml-3"
676
- :disabled="loading.checkingOut"
677
- @click="handleCheckout(selectedVisitorId as string)"
678
- >
679
- Check Out
680
- </AppButton>
681
- </span>
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
- <span
740
+ <div
684
741
  v-else-if="key === 'nric' && selectedVisitorObject['nric']"
685
- class="d-flex align-center"
742
+ class="visitor-preview__row"
686
743
  >
687
- <strong>{{ label }}:</strong>
688
- <span v-tooltip:top="selectedVisitorObject[key]" class="ml-2">{{
689
- maskNRIC(selectedVisitorObject[key])
690
- }}</span>
691
- </span>
692
- <span
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="d-flex flex-column"
758
+ class="visitor-preview__row visitor-preview__row--tall"
698
759
  >
699
- <strong class="w-full">{{ label }}:</strong>
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
- </span>
710
- <span
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' || key === 'snapshotExitImage'
782
+ (key === 'snapshotEntryImage' ||
783
+ key === 'snapshotExitImage') &&
784
+ selectedVisitorObject[key]
713
785
  "
714
- class="d-flex flex-column"
786
+ class="visitor-preview__row visitor-preview__row--tall"
715
787
  >
716
- <strong class="w-full">{{ label }}:</strong>
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
- </span>
797
+ </div>
730
798
 
731
- <span
799
+ <div
732
800
  v-else-if="selectedVisitorObject[key]"
733
- class="d-flex ga-3 align-start"
734
- ><strong>{{ label }}:</strong>
735
- {{ formatValues(key, selectedVisitorObject[key]) }}
736
- <TooltipInfo
737
- v-if="key === 'checkOut'"
738
- text="Manual Checkout"
739
- density="compact"
740
- size="x-small"
741
- />
742
- </span>
743
- </v-col>
744
- </v-row>
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
 
@@ -2739,6 +2821,62 @@ watchEffect(async () => {
2739
2821
  </script>
2740
2822
 
2741
2823
  <style scoped>
2824
+ /*
2825
+ * THE PREVIEW DIALOG'S FIELD LIST - LABEL BESIDE VALUE.
2826
+ *
2827
+ * The type comes from `AccessCardDetailsDialog` unchanged - the label on
2828
+ * `--fs-breadcrumb`/`--text2`, the value on `--fs-cell`/`--fw-cell`/`--text` -
2829
+ * so a field reads the same weight here as it does on an access card. The
2830
+ * arrangement is this dialog's: a FIXED 116px label column, so every value in
2831
+ * the card starts on one edge instead of wherever its own label happened to
2832
+ * end, which is what the old `d-flex ga-3` gave.
2833
+ *
2834
+ * `minmax(0, 1fr)` on the value track and not `1fr`: a grid track's default
2835
+ * `min-width: auto` refuses to shrink below its content, so a long company
2836
+ * name or a `Block / Level / Unit` string would push the card wider than the
2837
+ * 450px the dialog is opened at rather than wrap inside it.
2838
+ *
2839
+ * The hairline is the card's own `--border`, on the row and not between rows,
2840
+ * so it tracks the theme with everything else on the card. The last row drops
2841
+ * it - a rule directly above the footer's own edge reads as a double line.
2842
+ */
2843
+ .visitor-preview {
2844
+ display: flex;
2845
+ flex-direction: column;
2846
+ padding-bottom: 8px;
2847
+ }
2848
+
2849
+ .visitor-preview__row {
2850
+ display: grid;
2851
+ grid-template-columns: 116px minmax(0, 1fr);
2852
+ gap: 16px;
2853
+ align-items: baseline;
2854
+ padding: 9px 0;
2855
+ border-bottom: 1px solid var(--border);
2856
+ }
2857
+
2858
+ .visitor-preview__row:last-child {
2859
+ border-bottom: none;
2860
+ }
2861
+
2862
+ /* A chip, a 40px button and an attachment tile have no text baseline worth
2863
+ aligning a label to; those rows centre instead. */
2864
+ .visitor-preview__row--tall {
2865
+ align-items: center;
2866
+ }
2867
+
2868
+ .visitor-preview__label {
2869
+ font-size: var(--fs-breadcrumb);
2870
+ color: var(--text2);
2871
+ }
2872
+
2873
+ .visitor-preview__value {
2874
+ font-size: var(--fs-cell);
2875
+ font-weight: var(--fw-cell);
2876
+ color: var(--text);
2877
+ word-break: break-word;
2878
+ }
2879
+
2742
2880
  /* The width the type filter carried as `max-width="200"` on the v-select. */
2743
2881
  .visitor-type-filter {
2744
2882
  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.8",
5
+ "version": "4.2.9-staging.277",
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.",