@iservice365/layer-common 1.4.2 → 1.5.1
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/BillingMain.vue +66 -0
- package/components/Card/Toggle.vue +25 -0
- package/components/Dialog/UpdateMoreAction.vue +5 -1
- package/components/Editor.vue +61 -0
- package/components/ImageCarousel.vue +98 -0
- package/components/Input/FileV2.vue +81 -36
- package/components/ServiceProviderMain.vue +410 -70
- package/components/WorkOrder/Main.vue +156 -75
- package/composables/useFacility.ts +246 -0
- package/composables/useServiceProvider.ts +8 -2
- package/package.json +3 -1
- package/components/FacilityForm.vue +0 -226
- package/components/FacilityFormAvailability.vue +0 -13
- package/components/FacilityFormBookingFee.vue +0 -13
- package/components/FacilityFormBookingPolicy.vue +0 -13
- package/components/FacilityFormDepositFee.vue +0 -13
- package/components/FacilityFormDescription.vue +0 -13
- package/components/FacilityFormInformation.vue +0 -40
- package/components/FacilityFormRefund.vue +0 -13
- package/components/FacilityFormRulesAndPolicies.vue +0 -13
- package/components/FacilityFormSecurityChecklist.vue +0 -13
package/CHANGELOG.md
CHANGED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-row no-gutters>
|
|
3
|
+
<v-col cols="12">
|
|
4
|
+
<v-card
|
|
5
|
+
width="100%"
|
|
6
|
+
variant="outlined"
|
|
7
|
+
border="thin"
|
|
8
|
+
rounded="lg"
|
|
9
|
+
:loading="false"
|
|
10
|
+
>
|
|
11
|
+
<v-toolbar density="compact" color="grey-lighten-4">
|
|
12
|
+
<template #prepend>
|
|
13
|
+
<v-btn fab icon density="comfortable">
|
|
14
|
+
<v-icon>mdi-refresh</v-icon>
|
|
15
|
+
</v-btn>
|
|
16
|
+
</template>
|
|
17
|
+
<template #append>
|
|
18
|
+
<v-row no-gutters justify="end" align="center">
|
|
19
|
+
<span class="mr-2 text-caption text-fontgray">
|
|
20
|
+
{{ pageRange }}
|
|
21
|
+
</span>
|
|
22
|
+
<local-pagination v-model="page" :length="pages" />
|
|
23
|
+
</v-row>
|
|
24
|
+
</template>
|
|
25
|
+
</v-toolbar>
|
|
26
|
+
|
|
27
|
+
<v-data-table
|
|
28
|
+
:headers="headers"
|
|
29
|
+
:items="items"
|
|
30
|
+
item-value="_id"
|
|
31
|
+
items-per-page="20"
|
|
32
|
+
fixed-header
|
|
33
|
+
hide-default-footer
|
|
34
|
+
hide-default-header
|
|
35
|
+
style="max-height: calc(100vh - (180px))"
|
|
36
|
+
>
|
|
37
|
+
<template #item.permissions="{ value }">
|
|
38
|
+
<span class="text-caption font-weight-bold text-capitalize">
|
|
39
|
+
permissions
|
|
40
|
+
</span>
|
|
41
|
+
<v-chip>{{ value.length }}</v-chip>
|
|
42
|
+
</template>
|
|
43
|
+
|
|
44
|
+
<template #item.nature="{ item }">
|
|
45
|
+
<span class="text-capitalize">
|
|
46
|
+
{{ item.nature }}
|
|
47
|
+
</span>
|
|
48
|
+
</template>
|
|
49
|
+
</v-data-table>
|
|
50
|
+
</v-card>
|
|
51
|
+
</v-col>
|
|
52
|
+
</v-row>
|
|
53
|
+
</template>
|
|
54
|
+
|
|
55
|
+
<script setup lang="ts">
|
|
56
|
+
const page = ref(1);
|
|
57
|
+
const pages = ref(0);
|
|
58
|
+
const pageRange = ref("-- - -- of --");
|
|
59
|
+
|
|
60
|
+
const items = ref<Array<Record<string, any>>>([]);
|
|
61
|
+
|
|
62
|
+
const headers = [
|
|
63
|
+
{ title: "Name", value: "name" },
|
|
64
|
+
{ title: "Nature", value: "nature" },
|
|
65
|
+
];
|
|
66
|
+
</script>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-card flat border="md black">
|
|
3
|
+
<v-card-text class="d-flex justify-space-between align-center py-0">
|
|
4
|
+
<p>{{ label }}</p>
|
|
5
|
+
<v-switch v-model="toggle" :color="toggleColor" hide-details />
|
|
6
|
+
</v-card-text>
|
|
7
|
+
</v-card>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<script setup lang="ts">
|
|
11
|
+
defineProps({
|
|
12
|
+
label: {
|
|
13
|
+
type: String,
|
|
14
|
+
default: "Enable"
|
|
15
|
+
},
|
|
16
|
+
toggleColor: {
|
|
17
|
+
type: String,
|
|
18
|
+
default: 'primary'
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
const toggle = defineModel({default: false})
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<style scoped></style>
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
</template>
|
|
14
14
|
</v-toolbar>
|
|
15
15
|
|
|
16
|
-
<v-card-text style="max-height: 100vh; overflow-y: auto" class="pb-0">
|
|
16
|
+
<v-card-text style="max-height: 100vh; overflow-y: auto" class="pb-0" :class="contentNoPadding ? 'pa-0' : ''">
|
|
17
17
|
<slot name="content" />
|
|
18
18
|
</v-card-text>
|
|
19
19
|
|
|
@@ -90,6 +90,10 @@ const prop = defineProps({
|
|
|
90
90
|
type: String,
|
|
91
91
|
default: "Details",
|
|
92
92
|
},
|
|
93
|
+
contentNoPadding: {
|
|
94
|
+
type: Boolean,
|
|
95
|
+
default: false,
|
|
96
|
+
},
|
|
93
97
|
});
|
|
94
98
|
|
|
95
99
|
const emit = defineEmits(["close", "edit", "delete"]);
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<ClientOnly>
|
|
3
|
+
<v-row no-gutters class="ckeditor-container w-100">
|
|
4
|
+
<ckeditor v-model="data" :editor="ClassicEditor" :config="config" />
|
|
5
|
+
</v-row>
|
|
6
|
+
</ClientOnly>
|
|
7
|
+
</template>
|
|
8
|
+
|
|
9
|
+
<script setup>
|
|
10
|
+
import {
|
|
11
|
+
ClassicEditor,
|
|
12
|
+
Essentials,
|
|
13
|
+
Paragraph,
|
|
14
|
+
Bold,
|
|
15
|
+
Italic,
|
|
16
|
+
Heading,
|
|
17
|
+
List,
|
|
18
|
+
Link,
|
|
19
|
+
Alignment,
|
|
20
|
+
Font
|
|
21
|
+
} from 'ckeditor5'
|
|
22
|
+
import { Ckeditor } from '@ckeditor/ckeditor5-vue'
|
|
23
|
+
import 'ckeditor5/ckeditor5.css';
|
|
24
|
+
|
|
25
|
+
import 'ckeditor5/ckeditor5.css'
|
|
26
|
+
|
|
27
|
+
const data = defineModel({ required: true, default: "" })
|
|
28
|
+
|
|
29
|
+
const config = computed(() => ({
|
|
30
|
+
licenseKey: 'GPL',
|
|
31
|
+
plugins: [Essentials, Paragraph, Bold, Italic, Heading, List, Alignment, Font],
|
|
32
|
+
toolbar: [
|
|
33
|
+
'undo', 'redo',
|
|
34
|
+
'|', 'bold', 'italic',
|
|
35
|
+
'|', 'fontSize', 'fontColor', 'fontBackgroundColor',
|
|
36
|
+
'|', 'bulletedList', 'numberedList',
|
|
37
|
+
'|', 'heading', '|', 'alignment:left', 'alignment:center', 'alignment:right', 'alignment:justify',
|
|
38
|
+
]
|
|
39
|
+
}))
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<style scoped>
|
|
43
|
+
.ckeditor-container {
|
|
44
|
+
width: 100%;
|
|
45
|
+
max-width: 100% !important;
|
|
46
|
+
overflow: hidden;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
:deep(.ck.ck-editor) {
|
|
50
|
+
width: 100% !important;
|
|
51
|
+
max-width: 100%;
|
|
52
|
+
box-sizing: border-box;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
:deep(.ck-editor__editable_inline) {
|
|
56
|
+
min-height: 200px;
|
|
57
|
+
width: 100% !important;
|
|
58
|
+
box-sizing: border-box;
|
|
59
|
+
}
|
|
60
|
+
</style>
|
|
61
|
+
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-dialog v-model="overlay" v-if="overlay" width="100%" height="100%" opacity="50">
|
|
3
|
+
<v-row align="center" justify="center" class="fill-height" style="position: relative;">
|
|
4
|
+
<v-carousel hide-delimiters width="100%" height="100%" :show-arrows="images.length > 1 ? 'hover' : false"
|
|
5
|
+
v-model="activeIndex">
|
|
6
|
+
<template v-for="x, index in images" :key="x._id">
|
|
7
|
+
<v-carousel-item height="100%" width="100%" rounded="lg">
|
|
8
|
+
<v-row no-gutters class="w-100 h-100" align="center">
|
|
9
|
+
<v-img :lazy-src="getFileUrl(x)" :src="getFileUrl(x)" width="70%" height="70%" :alt="'Image Viewer Card -' + index"></v-img>
|
|
10
|
+
</v-row>
|
|
11
|
+
<template v-slot:placeholder>
|
|
12
|
+
<div class="d-flex align-center justify-center fill-height">
|
|
13
|
+
<v-progress-circular color="grey-lighten-4" indeterminate></v-progress-circular>
|
|
14
|
+
</div>
|
|
15
|
+
</template>
|
|
16
|
+
</v-carousel-item>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<template v-slot:prev="{ props }">
|
|
20
|
+
<v-btn color="white" variant="outlined" class="text-white" icon="mdi-chevron-left"
|
|
21
|
+
@click="props.onClick"></v-btn>
|
|
22
|
+
</template>
|
|
23
|
+
<template v-slot:next="{ props }">
|
|
24
|
+
<v-btn color="white" class="text-white" variant="outlined" icon="mdi-chevron-right"
|
|
25
|
+
@click="props.onClick"></v-btn>
|
|
26
|
+
</template>
|
|
27
|
+
</v-carousel>
|
|
28
|
+
<div style="position: absolute; top: 2%; left: 0%; right: 0%; z-index: 2"
|
|
29
|
+
class="cursor-pointer text-white text-h6 custom-shadow d-flex justify-space-between" @click="overlay = false">
|
|
30
|
+
<v-row no-gutters style="position: relative">
|
|
31
|
+
<v-col cols="8" xs="6" sm="2" md="2" lg="2">
|
|
32
|
+
<v-btn prepend-icon="mdi-close" text="Close" rounded="lg" color="secondary" class="ml-2"
|
|
33
|
+
style="position: absolute;"></v-btn>
|
|
34
|
+
</v-col>
|
|
35
|
+
|
|
36
|
+
</v-row>
|
|
37
|
+
|
|
38
|
+
</div>
|
|
39
|
+
<span class="text-white text-16px d-flex w-100 mt-2 justify-center" style="position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%);">{{ `${activeIndex + 1}/${images.length}`
|
|
40
|
+
}}</span>
|
|
41
|
+
|
|
42
|
+
</v-row>
|
|
43
|
+
</v-dialog>
|
|
44
|
+
</template>
|
|
45
|
+
|
|
46
|
+
<script setup lang="ts">
|
|
47
|
+
const props = defineProps({
|
|
48
|
+
activeImageId: {
|
|
49
|
+
type: String,
|
|
50
|
+
required: false,
|
|
51
|
+
},
|
|
52
|
+
isFavorite: {
|
|
53
|
+
type: Boolean,
|
|
54
|
+
default: false,
|
|
55
|
+
required: false
|
|
56
|
+
},
|
|
57
|
+
images: {
|
|
58
|
+
type: Array as PropType<string[]>,
|
|
59
|
+
default: []
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
const { getFileUrl } = useFile()
|
|
65
|
+
const overlay = defineModel({ required: true, default: false });
|
|
66
|
+
const activeIndex = ref(0);
|
|
67
|
+
const { currentUser} = useLocalAuth();
|
|
68
|
+
|
|
69
|
+
const emit = defineEmits(['share', 'like'])
|
|
70
|
+
|
|
71
|
+
watchEffect(() => {
|
|
72
|
+
if (props.activeImageId && props.images.length > 0) {
|
|
73
|
+
const index =
|
|
74
|
+
props.images?.findIndex((x) => x == props.activeImageId) || 0;
|
|
75
|
+
if (index !== -1) {
|
|
76
|
+
activeIndex.value = index;
|
|
77
|
+
} else activeIndex.value = 0;
|
|
78
|
+
} else {
|
|
79
|
+
return (activeIndex.value = 0);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
const handleShare = () => {
|
|
85
|
+
overlay.value = false;
|
|
86
|
+
emit('share')
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const handleLike = () => {
|
|
90
|
+
emit('like')
|
|
91
|
+
}
|
|
92
|
+
</script>
|
|
93
|
+
|
|
94
|
+
<style scoped>
|
|
95
|
+
.custom-shadow {
|
|
96
|
+
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.8);
|
|
97
|
+
}
|
|
98
|
+
</style>
|
|
@@ -1,26 +1,52 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
2
|
+
<v-row no-gutters class="w-100 pb-5" @click="resetErrorMessage">
|
|
3
|
+
|
|
4
|
+
<!-- VIEW MODE -->
|
|
5
|
+
<template v-if="viewMode">
|
|
6
|
+
<div class="w-100">
|
|
7
|
+
<v-file-upload-item v-for="({ file }, idx) in filesCollection" :key="fileKey(file)" :file="file" lines="one" nav
|
|
8
|
+
@click="handleClickItem(file)">
|
|
9
|
+
<template #prepend>
|
|
10
|
+
<v-avatar size="32" rounded></v-avatar>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<!-- delete hidden in view mode -->
|
|
14
|
+
<template #clear>
|
|
15
|
+
<!-- empty on purpose -->
|
|
16
|
+
</template>
|
|
17
|
+
</v-file-upload-item>
|
|
18
|
+
</div>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<!-- NORMAL MODE -->
|
|
22
|
+
<template v-else>
|
|
23
|
+
<v-file-upload v-model="uploadFiles" density="compact" @update:model-value="handleUpdateValue"
|
|
24
|
+
:loading="processing" :disabled="processing" :height="height" :title="title" :accept="accept"
|
|
25
|
+
:name="`upload_images`" class="text-caption w-100" clearable :multiple="multiple">
|
|
26
|
+
<template #item="{ props: itemProps, file }">
|
|
27
|
+
<v-file-upload-item v-bind="itemProps" lines="one" nav @click="handleClickItem(file)">
|
|
28
|
+
<template #prepend>
|
|
29
|
+
<v-avatar size="32" rounded></v-avatar>
|
|
16
30
|
</template>
|
|
17
|
-
|
|
18
|
-
|
|
31
|
+
|
|
32
|
+
<!-- delete button NOT shown in view mode -->
|
|
33
|
+
<template #clear="{ props: clearProps }">
|
|
34
|
+
<v-btn v-if="!viewMode" color="primary" @click.stop="handleRemove(file)"></v-btn>
|
|
35
|
+
</template>
|
|
36
|
+
</v-file-upload-item>
|
|
37
|
+
</template>
|
|
38
|
+
</v-file-upload>
|
|
39
|
+
</template>
|
|
40
|
+
|
|
41
|
+
<v-row no-gutters class="w-100" v-if="errorMessage">
|
|
19
42
|
<p class="text-error w-100 text-center text-subtitle-2">{{ errorMessage }}</p>
|
|
20
43
|
</v-row>
|
|
21
|
-
|
|
44
|
+
|
|
45
|
+
<ImageCarousel v-model="showImageCarousel" :images="idsArray" :active-image-id="activeImageId" />
|
|
46
|
+
</v-row>
|
|
22
47
|
</template>
|
|
23
48
|
|
|
49
|
+
|
|
24
50
|
<script setup lang="ts">
|
|
25
51
|
import { nextTick, ref, onMounted, watch } from 'vue'
|
|
26
52
|
|
|
@@ -44,11 +70,18 @@ const props = defineProps({
|
|
|
44
70
|
accept: {
|
|
45
71
|
type: String,
|
|
46
72
|
default: "image/*"
|
|
73
|
+
},
|
|
74
|
+
viewMode: {
|
|
75
|
+
type: Boolean,
|
|
76
|
+
default: false
|
|
47
77
|
}
|
|
48
78
|
})
|
|
49
79
|
|
|
50
80
|
const { addFile, deleteFile, getFileUrl, urlToFile } = useFile()
|
|
51
81
|
|
|
82
|
+
const showImageCarousel = ref(false)
|
|
83
|
+
const activeImageId = ref("")
|
|
84
|
+
|
|
52
85
|
// The parent v-model binding
|
|
53
86
|
const idsArray = defineModel<string[]>({ default: [] })
|
|
54
87
|
|
|
@@ -76,6 +109,21 @@ async function handleRemove(removedFile: File) {
|
|
|
76
109
|
|
|
77
110
|
}
|
|
78
111
|
|
|
112
|
+
function handleClickItem(file: File) {
|
|
113
|
+
const isImage =
|
|
114
|
+
file.type.startsWith('image/') ||
|
|
115
|
+
/\.(jpg|jpeg|png|gif|webp|bmp|svg)$/i.test(file.name)
|
|
116
|
+
|
|
117
|
+
if (!isImage) return
|
|
118
|
+
|
|
119
|
+
const found = filesCollection.value.find((item) => fileKey(item.file) === fileKey(file))
|
|
120
|
+
if (!found) return
|
|
121
|
+
|
|
122
|
+
activeImageId.value = found.id
|
|
123
|
+
showImageCarousel.value = true
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
|
|
79
127
|
async function handleUpdateValue(value: File[]) {
|
|
80
128
|
await nextTick()
|
|
81
129
|
const max = props.maxLength
|
|
@@ -95,12 +143,12 @@ async function handleUpdateValue(value: File[]) {
|
|
|
95
143
|
for (const file of addedFiles) {
|
|
96
144
|
const res = await addFile(file) // should return { id, url }
|
|
97
145
|
if (res?.id) {
|
|
98
|
-
filesCollection.value.
|
|
146
|
+
filesCollection.value = [...filesCollection.value, { file, id: res.id }]
|
|
99
147
|
}
|
|
100
148
|
}
|
|
101
149
|
|
|
102
150
|
uploadFiles.value = filesCollection.value.map((x) => x.file)
|
|
103
|
-
|
|
151
|
+
|
|
104
152
|
} catch (err) {
|
|
105
153
|
console.error('Upload failed', err)
|
|
106
154
|
errorMessage.value = 'Failed to upload some files.'
|
|
@@ -132,33 +180,30 @@ function resetErrorMessage() {
|
|
|
132
180
|
|
|
133
181
|
|
|
134
182
|
onMounted(async () => {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
183
|
+
setTimeout(async () => {
|
|
184
|
+
if (idsArray.value.length > 0) {
|
|
185
|
+
const loadedFiles = await loadFilesFromIds(idsArray.value)
|
|
186
|
+
filesCollection.value = loadedFiles
|
|
187
|
+
uploadFiles.value = loadedFiles.map((x) => x.file)
|
|
188
|
+
}
|
|
189
|
+
}, 1000)
|
|
142
190
|
})
|
|
143
191
|
|
|
192
|
+
watch(filesCollection, () => {
|
|
193
|
+
idsArray.value = [...filesCollection.value.map(x => x.id)]
|
|
194
|
+
}, { deep: true })
|
|
195
|
+
|
|
144
196
|
|
|
145
|
-
watch(
|
|
146
|
-
filesCollection,
|
|
147
|
-
(newVal) => {
|
|
148
|
-
idsArray.value = newVal.map((x) => x.id)
|
|
149
|
-
},
|
|
150
|
-
{ deep: true }
|
|
151
|
-
)
|
|
152
197
|
</script>
|
|
153
198
|
|
|
154
199
|
|
|
155
200
|
<style scoped>
|
|
156
201
|
* :deep(.v-file-upload-title) {
|
|
157
|
-
|
|
158
|
-
|
|
202
|
+
font-size: 1rem;
|
|
203
|
+
font-weight: 500;
|
|
159
204
|
}
|
|
160
205
|
|
|
161
206
|
* :deep(.v-file-upload-items) {
|
|
162
|
-
|
|
207
|
+
min-width: 100%;
|
|
163
208
|
}
|
|
164
209
|
</style>
|