@iservice365/layer-common 1.5.7 → 1.7.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.
- package/CHANGELOG.md +12 -0
- package/components/AccessCardAddForm.vue +363 -0
- package/components/AccessManagement.vue +414 -0
- package/components/BuildingUnitFormEdit.vue +47 -0
- package/components/DocumentForm.vue +67 -3
- package/components/DocumentManagement.vue +188 -4
- package/components/Editor.vue +59 -25
- package/components/EntryPassMain.vue +231 -0
- package/components/ImageCarousel.vue +74 -34
- package/components/Input/FileV2.vue +1 -1
- package/components/Input/InputPhoneNumberV2.vue +1 -0
- package/components/Input/NRICNumber.vue +2 -2
- package/components/Input/VehicleNumber.vue +1 -1
- package/components/Nfc/NFCTagForm.vue +210 -0
- package/components/Nfc/NFCTagMain.vue +342 -0
- package/components/VideoPlayer.vue +125 -0
- package/components/VisitorForm.vue +143 -62
- package/components/VisitorManagement.vue +1 -14
- package/composables/useCard.ts +46 -0
- package/composables/useDocument.ts +33 -13
- package/composables/useFile.ts +11 -5
- package/composables/useNFCPatrolTag.ts +51 -0
- package/composables/usePeople.ts +19 -1
- package/composables/useVisitor.ts +1 -1
- package/package.json +1 -1
- package/types/building.d.ts +2 -0
- package/types/card.d.ts +22 -0
- package/types/document.d.ts +1 -1
|
@@ -1,19 +1,43 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<v-dialog v-model="overlay" v-if="overlay" width="100%" height="100%" opacity="50">
|
|
3
3
|
<v-row align="center" justify="center" class="fill-height" style="position: relative;">
|
|
4
|
-
<v-carousel hide-delimiters width="100%" height="100%" :show-arrows="
|
|
4
|
+
<v-carousel hide-delimiters width="100%" height="100%" :show-arrows="files.length > 1 ? 'hover' : false"
|
|
5
5
|
v-model="activeIndex">
|
|
6
|
-
<template v-for="x, index in
|
|
7
|
-
<v-
|
|
8
|
-
<v-
|
|
9
|
-
<v-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
6
|
+
<template v-for="x, index in files" :key="x || index">
|
|
7
|
+
<template v-if="fileTypes?.[x] === 'image'">
|
|
8
|
+
<v-carousel-item height="100%" width="100%" rounded="lg">
|
|
9
|
+
<v-row no-gutters class="w-100 h-100" align="center">
|
|
10
|
+
<v-img :lazy-src="getFileUrl(x)" :src="getFileUrl(x)" width="70%" height="70%"
|
|
11
|
+
:alt="'Image Viewer Card -' + index"></v-img>
|
|
12
|
+
</v-row>
|
|
13
|
+
<template v-slot:placeholder>
|
|
14
|
+
<div class="d-flex align-center justify-center fill-height">
|
|
15
|
+
<v-progress-circular color="grey-lighten-4" indeterminate></v-progress-circular>
|
|
16
|
+
</div>
|
|
17
|
+
</template>
|
|
18
|
+
</v-carousel-item>
|
|
19
|
+
</template>
|
|
20
|
+
<template v-else-if="fileTypes?.[x] === 'video'">
|
|
21
|
+
<v-carousel-item>
|
|
22
|
+
<v-row no-gutters class="h-100 w-100" align="center" justify="center">
|
|
23
|
+
<video width="80%" height="80%" controls>
|
|
24
|
+
<source :src="getFileUrl(x)" />
|
|
25
|
+
</video>
|
|
26
|
+
</v-row>
|
|
27
|
+
<template v-slot:placeholder>
|
|
28
|
+
<div class="d-flex align-center justify-center fill-height">
|
|
29
|
+
<v-progress-circular color="grey-lighten-4" indeterminate></v-progress-circular>
|
|
30
|
+
</div>
|
|
31
|
+
</template>
|
|
32
|
+
</v-carousel-item>
|
|
33
|
+
</template>
|
|
34
|
+
<template v-else>
|
|
35
|
+
<v-carousel-item>
|
|
36
|
+
<v-row no-gutters class="h-100 w-100" align="center" justify="center">
|
|
37
|
+
<v-icon size="100" color="white">mdi-file</v-icon>
|
|
38
|
+
</v-row>
|
|
39
|
+
</v-carousel-item>
|
|
40
|
+
</template>
|
|
17
41
|
</template>
|
|
18
42
|
|
|
19
43
|
<template v-slot:prev="{ props }">
|
|
@@ -29,15 +53,17 @@
|
|
|
29
53
|
class="cursor-pointer text-white text-h6 custom-shadow d-flex justify-space-between" @click="overlay = false">
|
|
30
54
|
<v-row no-gutters style="position: relative">
|
|
31
55
|
<v-col cols="8" xs="6" sm="2" md="2" lg="2">
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
56
|
+
<v-btn prepend-icon="mdi-close" text="Close" rounded="lg" color="secondary" class="ml-2"
|
|
57
|
+
style="position: absolute;"></v-btn>
|
|
58
|
+
</v-col>
|
|
35
59
|
|
|
36
60
|
</v-row>
|
|
37
61
|
|
|
38
62
|
</div>
|
|
39
|
-
<span class="text-white text-16px d-flex w-100 mt-2 justify-center"
|
|
40
|
-
|
|
63
|
+
<span class="text-white text-16px d-flex w-100 mt-2 justify-center"
|
|
64
|
+
style="position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%);">{{ `${activeIndex +
|
|
65
|
+
1}/${files.length}`
|
|
66
|
+
}}</span>
|
|
41
67
|
|
|
42
68
|
</v-row>
|
|
43
69
|
</v-dialog>
|
|
@@ -45,33 +71,29 @@
|
|
|
45
71
|
|
|
46
72
|
<script setup lang="ts">
|
|
47
73
|
const props = defineProps({
|
|
48
|
-
|
|
74
|
+
activeFileId: {
|
|
49
75
|
type: String,
|
|
50
76
|
required: false,
|
|
51
77
|
},
|
|
52
|
-
|
|
53
|
-
type: Boolean,
|
|
54
|
-
default: false,
|
|
55
|
-
required: false
|
|
56
|
-
},
|
|
57
|
-
images: {
|
|
78
|
+
files: {
|
|
58
79
|
type: Array as PropType<string[]>,
|
|
59
80
|
default: []
|
|
60
81
|
}
|
|
61
82
|
});
|
|
62
83
|
|
|
63
84
|
|
|
64
|
-
const { getFileUrl } = useFile()
|
|
85
|
+
const { getFileUrl, urlToFile } = useFile()
|
|
65
86
|
const overlay = defineModel({ required: true, default: false });
|
|
66
87
|
const activeIndex = ref(0);
|
|
67
|
-
const
|
|
88
|
+
const fileTypes = ref<Record<string, "image" | "video" | "other">>({});
|
|
89
|
+
|
|
68
90
|
|
|
69
91
|
const emit = defineEmits(['share', 'like'])
|
|
70
92
|
|
|
71
93
|
watchEffect(() => {
|
|
72
|
-
if (props.
|
|
94
|
+
if (props.activeFileId && props.files.length > 0) {
|
|
73
95
|
const index =
|
|
74
|
-
props.
|
|
96
|
+
props.files?.findIndex((x) => x == props.activeFileId) || 0;
|
|
75
97
|
if (index !== -1) {
|
|
76
98
|
activeIndex.value = index;
|
|
77
99
|
} else activeIndex.value = 0;
|
|
@@ -80,15 +102,33 @@ watchEffect(() => {
|
|
|
80
102
|
}
|
|
81
103
|
});
|
|
82
104
|
|
|
105
|
+
async function resolveFileTypes() {
|
|
106
|
+
fileTypes.value = {}; // reset
|
|
83
107
|
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
108
|
+
for (const x of props.files) {
|
|
109
|
+
try {
|
|
110
|
+
const url = getFileUrl(x);
|
|
111
|
+
const file = await urlToFile(url, x);
|
|
112
|
+
const type = file?.type;
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
if (type?.startsWith("video")) fileTypes.value[x] = "video";
|
|
116
|
+
else if (type?.startsWith("image")) fileTypes.value[x] = "image";
|
|
117
|
+
else fileTypes.value[x] = "other";
|
|
88
118
|
|
|
89
|
-
|
|
90
|
-
|
|
119
|
+
} catch (err) {
|
|
120
|
+
fileTypes.value[x] = "other";
|
|
121
|
+
}
|
|
122
|
+
}
|
|
91
123
|
}
|
|
124
|
+
|
|
125
|
+
watch(
|
|
126
|
+
() => props.files,
|
|
127
|
+
() => resolveFileTypes(),
|
|
128
|
+
{ immediate: true }
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
|
|
92
132
|
</script>
|
|
93
133
|
|
|
94
134
|
<style scoped>
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
<p class="text-error w-100 text-center text-subtitle-2">{{ errorMessage }}</p>
|
|
43
43
|
</v-row>
|
|
44
44
|
|
|
45
|
-
<ImageCarousel v-model="showImageCarousel" :
|
|
45
|
+
<ImageCarousel v-model="showImageCarousel" :files="idsArray" :active-file-id="activeImageId" />
|
|
46
46
|
</v-row>
|
|
47
47
|
</template>
|
|
48
48
|
|
|
@@ -35,7 +35,7 @@ const props = defineProps({
|
|
|
35
35
|
|
|
36
36
|
const emit = defineEmits(['update:modelValue'])
|
|
37
37
|
|
|
38
|
-
const model = defineModel({required: true
|
|
38
|
+
const model = defineModel({required: true})
|
|
39
39
|
|
|
40
40
|
function onInput(event) {
|
|
41
41
|
const value = typeof event === 'string' ? event : event?.target?.value || ''
|
|
@@ -43,7 +43,7 @@ function onInput(event) {
|
|
|
43
43
|
let formatted = value.replace(/[^A-Za-z0-9]/g, '')
|
|
44
44
|
formatted = formatted.toUpperCase()
|
|
45
45
|
|
|
46
|
-
model.value = formatted
|
|
46
|
+
model.value = formatted || ""
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
|
|
@@ -36,7 +36,7 @@ const props = defineProps({
|
|
|
36
36
|
}
|
|
37
37
|
})
|
|
38
38
|
|
|
39
|
-
const model = defineModel({required: true
|
|
39
|
+
const model = defineModel({required: true })
|
|
40
40
|
|
|
41
41
|
function onInput(event) {
|
|
42
42
|
const value = typeof event === 'string' ? event : event?.target?.value || ''
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-card width="100%">
|
|
3
|
+
<v-toolbar>
|
|
4
|
+
<v-row no-gutters class="fill-height px-6" align="center">
|
|
5
|
+
<span class="font-weight-bold text-h5 text-capitalize">
|
|
6
|
+
{{ title }}
|
|
7
|
+
</span>
|
|
8
|
+
</v-row>
|
|
9
|
+
</v-toolbar>
|
|
10
|
+
|
|
11
|
+
<v-card-text
|
|
12
|
+
style="max-height: 100vh; overflow-y: auto"
|
|
13
|
+
class="pa-5 my-5 px-7"
|
|
14
|
+
>
|
|
15
|
+
<v-form ref="formRef" v-model="valid" @submit.prevent="submit">
|
|
16
|
+
<v-row no-gutters>
|
|
17
|
+
<v-col cols="12" class="mb-4">
|
|
18
|
+
<label class="text-subtitle-2 font-weight-medium mb-2">
|
|
19
|
+
Name <span class="text-red">*</span>
|
|
20
|
+
</label>
|
|
21
|
+
<v-text-field
|
|
22
|
+
v-model="form.name"
|
|
23
|
+
placeholder="Enter name"
|
|
24
|
+
variant="outlined"
|
|
25
|
+
density="comfortable"
|
|
26
|
+
:rules="[rules.required]"
|
|
27
|
+
hide-details="auto"
|
|
28
|
+
/>
|
|
29
|
+
</v-col>
|
|
30
|
+
|
|
31
|
+
<v-col cols="12" class="mb-4">
|
|
32
|
+
<label class="text-subtitle-2 font-weight-medium mb-2">
|
|
33
|
+
Tag ID <span class="text-red">*</span>
|
|
34
|
+
</label>
|
|
35
|
+
<v-text-field
|
|
36
|
+
v-model="form.tagID"
|
|
37
|
+
placeholder="Enter tag id"
|
|
38
|
+
variant="outlined"
|
|
39
|
+
density="comfortable"
|
|
40
|
+
:rules="[rules.required, rules.noSpaces]"
|
|
41
|
+
hide-details="auto"
|
|
42
|
+
@input="handleTagIDInput"
|
|
43
|
+
/>
|
|
44
|
+
</v-col>
|
|
45
|
+
|
|
46
|
+
<v-col cols="12" class="mb-4" v-if="mode === 'edit'">
|
|
47
|
+
<label class="text-subtitle-2 font-weight-medium mb-2">
|
|
48
|
+
Status
|
|
49
|
+
</label>
|
|
50
|
+
<v-select
|
|
51
|
+
v-model="form.status"
|
|
52
|
+
:items="statusOptions"
|
|
53
|
+
variant="outlined"
|
|
54
|
+
density="comfortable"
|
|
55
|
+
hide-details="auto"
|
|
56
|
+
/>
|
|
57
|
+
</v-col>
|
|
58
|
+
</v-row>
|
|
59
|
+
</v-form>
|
|
60
|
+
</v-card-text>
|
|
61
|
+
|
|
62
|
+
<v-toolbar class="pa-0" density="compact">
|
|
63
|
+
<v-row no-gutters>
|
|
64
|
+
<v-col cols="6" class="pa-0">
|
|
65
|
+
<v-btn
|
|
66
|
+
block
|
|
67
|
+
variant="text"
|
|
68
|
+
class="text-none"
|
|
69
|
+
size="large"
|
|
70
|
+
@click="$emit('cancel')"
|
|
71
|
+
height="48"
|
|
72
|
+
:disabled="loading"
|
|
73
|
+
>
|
|
74
|
+
Cancel
|
|
75
|
+
</v-btn>
|
|
76
|
+
</v-col>
|
|
77
|
+
|
|
78
|
+
<v-col cols="6" class="pa-0">
|
|
79
|
+
<v-btn
|
|
80
|
+
block
|
|
81
|
+
tile
|
|
82
|
+
variant="flat"
|
|
83
|
+
class="text-none"
|
|
84
|
+
size="large"
|
|
85
|
+
height="48"
|
|
86
|
+
color="black"
|
|
87
|
+
@click="submit"
|
|
88
|
+
:loading="loading"
|
|
89
|
+
:disabled="!valid || loading"
|
|
90
|
+
>
|
|
91
|
+
Submit
|
|
92
|
+
</v-btn>
|
|
93
|
+
</v-col>
|
|
94
|
+
</v-row>
|
|
95
|
+
</v-toolbar>
|
|
96
|
+
</v-card>
|
|
97
|
+
</template>
|
|
98
|
+
|
|
99
|
+
<script setup lang="ts">
|
|
100
|
+
|
|
101
|
+
import useNFCPatrolTag from "../../composables/useNFCPatrolTag";
|
|
102
|
+
|
|
103
|
+
const props = defineProps({
|
|
104
|
+
title: {
|
|
105
|
+
type: String,
|
|
106
|
+
default: "Add NFC Tag",
|
|
107
|
+
},
|
|
108
|
+
site: {
|
|
109
|
+
type: String,
|
|
110
|
+
required: true,
|
|
111
|
+
},
|
|
112
|
+
orgId: {
|
|
113
|
+
type: String,
|
|
114
|
+
required: true,
|
|
115
|
+
},
|
|
116
|
+
mode: {
|
|
117
|
+
type: String as PropType<"create" | "edit">,
|
|
118
|
+
default: "create",
|
|
119
|
+
},
|
|
120
|
+
nfcTag: {
|
|
121
|
+
type: Object as PropType<any>,
|
|
122
|
+
default: () => ({
|
|
123
|
+
name: "",
|
|
124
|
+
tagID: "",
|
|
125
|
+
status: "active",
|
|
126
|
+
}),
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const emit = defineEmits(["cancel", "success", "error"]);
|
|
131
|
+
|
|
132
|
+
const formRef = ref();
|
|
133
|
+
const valid = ref(false);
|
|
134
|
+
const loading = ref(false);
|
|
135
|
+
|
|
136
|
+
const form = ref({
|
|
137
|
+
name: "",
|
|
138
|
+
tagID: "",
|
|
139
|
+
status: "active",
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const statusOptions = [
|
|
143
|
+
{ title: "Active", value: "active" },
|
|
144
|
+
{ title: "Inactive", value: "inactive" },
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
const rules = {
|
|
148
|
+
required: (v: any) => !!v || "This field is required",
|
|
149
|
+
noSpaces: (v: any) => !/\s/.test(v) || "Spaces are not allowed",
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// Initialize form with existing data in edit mode
|
|
153
|
+
watchEffect(() => {
|
|
154
|
+
if (props.mode === "edit" && props.nfcTag) {
|
|
155
|
+
form.value = {
|
|
156
|
+
name: props.nfcTag.name || "",
|
|
157
|
+
tagID: props.nfcTag.tagID || "",
|
|
158
|
+
status: props.nfcTag.status || "active",
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const { add: addNFCTag, updateById: updateNFCTag } = useNFCPatrolTag();
|
|
164
|
+
|
|
165
|
+
function handleTagIDInput(event: any) {
|
|
166
|
+
// Convert to uppercase and remove spaces
|
|
167
|
+
const value = event.target.value;
|
|
168
|
+
form.value.tagID = value.toUpperCase().replace(/\s/g, "");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function submit() {
|
|
172
|
+
const { valid: isValid } = await formRef.value.validate();
|
|
173
|
+
|
|
174
|
+
if (!isValid) return;
|
|
175
|
+
|
|
176
|
+
loading.value = true;
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
|
|
180
|
+
const payload: { site: string; tagID: string; name: string } = {
|
|
181
|
+
site: props.site,
|
|
182
|
+
tagID: form.value.tagID,
|
|
183
|
+
name: form.value.name,
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
if (props.mode === "create") {
|
|
187
|
+
await addNFCTag(payload);
|
|
188
|
+
} else {
|
|
189
|
+
await updateNFCTag(props.nfcTag._id, payload);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
emit("success");
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
if (props.mode === "create") {
|
|
196
|
+
form.value = {
|
|
197
|
+
name: "",
|
|
198
|
+
tagID: "",
|
|
199
|
+
status: "active",
|
|
200
|
+
};
|
|
201
|
+
formRef.value.reset();
|
|
202
|
+
}
|
|
203
|
+
} catch (error: any) {
|
|
204
|
+
console.error("Error submitting NFC tag:", error);
|
|
205
|
+
emit("error", error);
|
|
206
|
+
} finally {
|
|
207
|
+
loading.value = false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
</script>
|