@iservice365/layer-common 1.1.0 → 1.3.0

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.
Files changed (47) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/components/CameraForm.vue +264 -0
  3. package/components/CameraMain.vue +352 -0
  4. package/components/Card/DeleteConfirmation.vue +51 -0
  5. package/components/Card/MemberInfoSummary.vue +44 -0
  6. package/components/Chat/Information.vue +28 -11
  7. package/components/Dialog/DeleteConfirmation.vue +51 -0
  8. package/components/Dialog/UpdateMoreAction.vue +99 -0
  9. package/components/Feedback/Form.vue +17 -3
  10. package/components/FeedbackDetail.vue +0 -11
  11. package/components/FeedbackMain.vue +21 -11
  12. package/components/Input/DateTimePicker.vue +10 -5
  13. package/components/Input/File.vue +1 -1
  14. package/components/Input/FileV2.vue +106 -63
  15. package/components/Input/InputPhoneNumberV2.vue +114 -0
  16. package/components/Input/NRICNumber.vue +41 -0
  17. package/components/Input/PhoneNumber.vue +1 -0
  18. package/components/Input/VehicleNumber.vue +49 -0
  19. package/components/NumberSettingField.vue +107 -0
  20. package/components/PeopleForm.vue +452 -0
  21. package/components/TableMain.vue +2 -1
  22. package/components/VehicleUpdateMoreAction.vue +84 -0
  23. package/components/VisitorForm.vue +712 -0
  24. package/components/VisitorFormSelection.vue +53 -0
  25. package/components/VisitorManagement.vue +569 -0
  26. package/components/WorkOrder/Create.vue +87 -49
  27. package/components/WorkOrder/Main.vue +17 -12
  28. package/composables/useBuilding.ts +250 -0
  29. package/composables/useBuildingUnit.ts +116 -0
  30. package/composables/useFeedback.ts +3 -3
  31. package/composables/useFile.ts +7 -9
  32. package/composables/useLocal.ts +67 -0
  33. package/composables/usePeople.ts +48 -0
  34. package/composables/useSecurityUtils.ts +18 -0
  35. package/composables/useSiteSettings.ts +111 -0
  36. package/composables/useUtils.ts +30 -1
  37. package/composables/useVisitor.ts +80 -0
  38. package/composables/useWorkOrder.ts +3 -3
  39. package/package.json +1 -1
  40. package/plugins/vuetify.ts +6 -1
  41. package/types/building.d.ts +19 -0
  42. package/types/camera.d.ts +31 -0
  43. package/types/people.d.ts +22 -0
  44. package/types/select.d.ts +4 -0
  45. package/types/site.d.ts +10 -7
  46. package/types/visitor.d.ts +42 -0
  47. package/utils/phoneMasks.ts +1703 -0
@@ -0,0 +1,114 @@
1
+ <template>
2
+ <v-row no-gutters class="mb-5">
3
+ <v-col cols="12" class="d-flex ga-2">
4
+ <v-select v-model="selectedCode" :variant="variant" :items="countries" item-title="code" item-value="code"
5
+ hide-details class="px-0" :density="density" style="max-width: 95px" :rules="[...props.rules]"
6
+ @update:model-value="handleUpdateCountry">
7
+ <template v-slot:item="{ props: itemProps, item }">
8
+ <v-list-item v-bind="itemProps" :title="item.raw.name" :subtitle="item.raw.dial_code" width="300" />
9
+ </template>
10
+ </v-select>
11
+ <v-mask-input v-model="phone" :mask="currentMask" :rules="[...props.rules, validatePhone]"
12
+ :variant="variant" hint="Enter a valid phone number" hide-details persistent-hint return-masked-value
13
+ :density="density" :placeholder="placeholder || currentMask"></v-mask-input>
14
+ </v-col>
15
+ <span class="text-error text-caption w-100" v-if="errorMessage && !hideDetails">{{ errorMessage }}</span>
16
+ </v-row>
17
+
18
+ </template>
19
+
20
+ <script setup lang="ts">
21
+ import { ref, computed, type PropType } from 'vue'
22
+ import type { ValidationRule } from 'vuetify/lib/types.mjs'
23
+ import phoneMasks from '~/utils/phoneMasks'
24
+
25
+ const props = defineProps({
26
+ rules: {
27
+ type: Array as PropType<ValidationRule[]>,
28
+ default: []
29
+ },
30
+ variant: {
31
+ type: String as PropType<any>,
32
+ default: "outlined"
33
+ },
34
+ density: {
35
+ type: String as PropType<Density>,
36
+ default: "default"
37
+ },
38
+ placeholder: {
39
+ type: String,
40
+ },
41
+ hideDetails: {
42
+ type: Boolean,
43
+ default: false
44
+ }
45
+ })
46
+
47
+ type Density = 'default' | 'comfortable' | 'compact';
48
+
49
+ type TPhoneMask =
50
+ {
51
+ name: string
52
+ flag: string
53
+ code: string;
54
+ dial_code: string;
55
+ regex: string;
56
+ }
57
+
58
+ const phone = defineModel({ default: '' })
59
+ const selectedCode = ref('SG')
60
+ const countries = phoneMasks
61
+ const errorMessage = ref('')
62
+
63
+ const currentMask = computed(() => {
64
+ const country = phoneMasks.find((c: TPhoneMask) => c.code === selectedCode.value)
65
+ if (!country) return '############'
66
+ return generateMaskFromRegex(country.regex)
67
+ })
68
+
69
+
70
+
71
+ const validatePhone = (value: string): boolean | string => {
72
+ if (!value) {
73
+ errorMessage.value = ''
74
+ return true;
75
+ }
76
+ const country = phoneMasks.find((c: any) => c.code === selectedCode.value)
77
+ if (!country) {
78
+ errorMessage.value = ''
79
+ return true
80
+ }
81
+
82
+ const regex = new RegExp(country.regex)
83
+ const isValid = regex.test(value)
84
+
85
+ errorMessage.value = isValid ? '' : `Invalid ${country.name} phone number`
86
+ return isValid
87
+ }
88
+
89
+
90
+
91
+
92
+ function generateMaskFromRegex(regex: string): string {
93
+
94
+ let pattern = regex.replace(/^\^|\$$/g, '')
95
+ pattern = pattern.replace(/\\d\{(\d+)\}/g, (_, count) => '#'.repeat(Number(count)))
96
+ pattern = pattern.replace(/\\d/g, '#')
97
+
98
+ pattern = pattern.replace(/\\/g, '')
99
+
100
+ pattern = pattern.replace(/\s\?/g, ' ')
101
+ pattern = pattern.replace(/\s/g, ' ')
102
+
103
+ pattern = pattern.replace(/\(\?:/g, '(')
104
+
105
+ return pattern.trim()
106
+ }
107
+
108
+ function handleUpdateCountry() {
109
+ phone.value = ''
110
+ }
111
+
112
+
113
+
114
+ </script>
@@ -0,0 +1,41 @@
1
+ <template>
2
+ <v-text-field
3
+ v-model.trim="model"
4
+ :rules="rules"
5
+ :maxlength="maxlength"
6
+ :placeholder="placeholder"
7
+ :counter="maxlength"
8
+ @input="onInput"
9
+ outlined
10
+ clearable
11
+ />
12
+ </template>
13
+
14
+ <script setup>
15
+
16
+ const props = defineProps({
17
+ placeholder: {
18
+ type: String,
19
+ default: ''
20
+ },
21
+ rules: {
22
+ type: Array,
23
+ default: () => []
24
+ },
25
+ maxlength: {
26
+ type: [Number, String],
27
+ default: false
28
+ }
29
+ })
30
+
31
+ const model = defineModel({required: true, default: ""})
32
+
33
+ function onInput(event) {
34
+ const value = typeof event === 'string' ? event : event?.target?.value || ''
35
+
36
+ let formatted = value.replace(/[^A-Za-z0-9]/g, '')
37
+ formatted = formatted.toUpperCase()
38
+
39
+ model.value = formatted
40
+ }
41
+ </script>
@@ -14,6 +14,7 @@
14
14
  z-index="100000000"
15
15
  :menu-props="{
16
16
  maxHeight: 300,
17
+ zIndex: 3000
17
18
  }"
18
19
  no-data-text="No countries available"
19
20
  >
@@ -0,0 +1,49 @@
1
+ <template>
2
+ <v-text-field
3
+ v-model="model"
4
+ :rules="rules"
5
+ :maxlength="maxlength"
6
+ :placeholder="placeholder"
7
+ :counter="maxlength"
8
+ @input="onInput"
9
+ outlined
10
+ :disabled="disabled"
11
+ :readonly="readonly"
12
+ clearable
13
+ />
14
+ </template>
15
+
16
+ <script setup>
17
+
18
+ const props = defineProps({
19
+ placeholder: {
20
+ type: String,
21
+ default: 'Vehicle Number'
22
+ },
23
+ rules: {
24
+ type: Array,
25
+ default: () => []
26
+ },
27
+ maxlength: {
28
+ type: [Number, String],
29
+ default: false
30
+ },
31
+ disabled: {
32
+ type: Boolean,
33
+ },
34
+ readonly: {
35
+ type: Boolean,
36
+ }
37
+ })
38
+
39
+ const model = defineModel({required: true, default: ""})
40
+
41
+ function onInput(event) {
42
+ const value = typeof event === 'string' ? event : event?.target?.value || ''
43
+
44
+ let formatted = value.replace(/[^A-Za-z0-9]/g, '')
45
+ formatted = formatted.toUpperCase()
46
+
47
+ model.value = formatted
48
+ }
49
+ </script>
@@ -0,0 +1,107 @@
1
+ <template>
2
+ <v-row no-gutters>
3
+ <v-form v-model="valid">
4
+ <v-row>
5
+ <v-col cols="6">
6
+ <InputLabel class="text-capitalize font-weight-bold" :title="title" :required="required" />
7
+ <v-text-field v-model.number="count" type="number" density="comfortable" :rules="rules" />
8
+ </v-col>
9
+
10
+ <v-col cols="6">
11
+ <v-btn color="primary" class="text-none mt-6" size="large" variant="flat"
12
+ :disabled="!valid || disabled" :loading="loading || updating" text="Save" @click="handleSave" />
13
+ </v-col>
14
+ </v-row>
15
+ </v-form>
16
+ <Snackbar v-model="toastObject.show" :text="toastObject.message" :color="toastObject.color" />
17
+ </v-row>
18
+ </template>
19
+
20
+ <script setup lang="ts">
21
+ import useSiteSettings from '../composables/useSiteSettings';
22
+
23
+ const props = defineProps({
24
+ title: { type: String, required: true },
25
+ required: { type: Boolean, default: true },
26
+ loading: { type: Boolean, default: false },
27
+ type: { type: String as PropType<'blocks' | 'guard_posts'>, required: true},
28
+ disabled: {type: Boolean, default: false},
29
+ siteId: { type: String, required: true }
30
+ });
31
+
32
+ const { updateSite, setSiteGuardPosts } = useSiteSettings()
33
+
34
+
35
+ const count = defineModel<number>({ default: 0 });
36
+ const valid = ref(false);
37
+ const updating = ref(false)
38
+
39
+ const toastObject = reactive({
40
+ show: false,
41
+ message: "",
42
+ color: "",
43
+ });
44
+
45
+
46
+ const emits = defineEmits(["success", "error"]);
47
+
48
+
49
+ const { requiredRule } = useUtils();
50
+
51
+ function validNumber(value: number) {
52
+ return value > 0 || "Number must be greater than zero.";
53
+ }
54
+
55
+ const rules = computed(() => {
56
+ const arr = [];
57
+ if (props.required) {
58
+ arr.push(requiredRule);
59
+ arr.push(validNumber);
60
+ }
61
+ return arr;
62
+ });
63
+
64
+
65
+
66
+
67
+ async function handleSave() {
68
+ if(!props.siteId){
69
+ return emits('error', 'Site ID is required')
70
+ }
71
+
72
+ updating.value = true;
73
+
74
+ try {
75
+ if(props.type === 'blocks'){
76
+ await updateSite(props.siteId, { block: count.value});
77
+ handleSuccess('The number of blocks has been updated successfully!')
78
+ } else if (props.type) {
79
+ await setSiteGuardPosts(props.siteId, count.value)
80
+ handleSuccess('The number of guard posts has been updated successfully!')
81
+
82
+ } else {
83
+ throw new Error('Invalid type Prop provided.')
84
+ }
85
+
86
+ emits('success')
87
+ } catch (error: any) {
88
+ const message = error?.data?.message || `Error updating ${props.type.replace('_', ' ')}`;
89
+ handleError(message)
90
+ } finally {
91
+ updating.value = false;
92
+ }
93
+ }
94
+
95
+ const handleSuccess = (message: string) => {
96
+ toastObject.show = true;
97
+ toastObject.message = message;
98
+ toastObject.color = "success";
99
+ };
100
+
101
+ const handleError = (message: string) => {
102
+ toastObject.show = true;
103
+ toastObject.message = message;
104
+ toastObject.color = "error";
105
+ };
106
+
107
+ </script>