@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.
- package/CHANGELOG.md +21 -0
- package/components/CameraForm.vue +264 -0
- package/components/CameraMain.vue +352 -0
- package/components/Card/DeleteConfirmation.vue +51 -0
- package/components/Card/MemberInfoSummary.vue +44 -0
- package/components/Chat/Information.vue +28 -11
- package/components/Dialog/DeleteConfirmation.vue +51 -0
- package/components/Dialog/UpdateMoreAction.vue +99 -0
- package/components/Feedback/Form.vue +17 -3
- package/components/FeedbackDetail.vue +0 -11
- package/components/FeedbackMain.vue +21 -11
- package/components/Input/DateTimePicker.vue +10 -5
- package/components/Input/File.vue +1 -1
- package/components/Input/FileV2.vue +106 -63
- package/components/Input/InputPhoneNumberV2.vue +114 -0
- package/components/Input/NRICNumber.vue +41 -0
- package/components/Input/PhoneNumber.vue +1 -0
- package/components/Input/VehicleNumber.vue +49 -0
- package/components/NumberSettingField.vue +107 -0
- package/components/PeopleForm.vue +452 -0
- package/components/TableMain.vue +2 -1
- package/components/VehicleUpdateMoreAction.vue +84 -0
- package/components/VisitorForm.vue +712 -0
- package/components/VisitorFormSelection.vue +53 -0
- package/components/VisitorManagement.vue +569 -0
- package/components/WorkOrder/Create.vue +87 -49
- package/components/WorkOrder/Main.vue +17 -12
- package/composables/useBuilding.ts +250 -0
- package/composables/useBuildingUnit.ts +116 -0
- package/composables/useFeedback.ts +3 -3
- package/composables/useFile.ts +7 -9
- package/composables/useLocal.ts +67 -0
- package/composables/usePeople.ts +48 -0
- package/composables/useSecurityUtils.ts +18 -0
- package/composables/useSiteSettings.ts +111 -0
- package/composables/useUtils.ts +30 -1
- package/composables/useVisitor.ts +80 -0
- package/composables/useWorkOrder.ts +3 -3
- package/package.json +1 -1
- package/plugins/vuetify.ts +6 -1
- package/types/building.d.ts +19 -0
- package/types/camera.d.ts +31 -0
- package/types/people.d.ts +22 -0
- package/types/select.d.ts +4 -0
- package/types/site.d.ts +10 -7
- package/types/visitor.d.ts +42 -0
- package/utils/phoneMasks.ts +1703 -0
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-card width="100%" :loading="processing">
|
|
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
|
+
{{ prop.mode }} {{ prop.type === 'visitor' ? 'Guest' : prop.type }}
|
|
7
|
+
</span>
|
|
8
|
+
</v-row>
|
|
9
|
+
</v-toolbar>
|
|
10
|
+
<v-card-text style="max-height: 100vh; overflow-y: auto" class="pa-5 my-3">
|
|
11
|
+
<div class="w-100 d-flex justify-space-between ga-2">
|
|
12
|
+
<span v class="text-subtitle-1 w-100 font-weight-bold mb-3">
|
|
13
|
+
<span v-if="step === 1">General Information</span>
|
|
14
|
+
<span v-else-if="step === 2">Pass Information</span>
|
|
15
|
+
</span>
|
|
16
|
+
<span class="text-subtitle-2 font-weight-bold" style="text-wrap: nowrap;">Step
|
|
17
|
+
<span class="text-primary-button">{{ step }}</span>/2</span>
|
|
18
|
+
</div>
|
|
19
|
+
<v-form ref="formRef" v-model="validForm" :disabled="processing" @click="errorMessage = ''">
|
|
20
|
+
<v-row no-gutters class="pt-4">
|
|
21
|
+
|
|
22
|
+
<template v-if="step === 1">
|
|
23
|
+
<v-col cols="12">
|
|
24
|
+
<v-row>
|
|
25
|
+
<v-col cols="12">
|
|
26
|
+
<InputLabel class="text-capitalize" title="Full Name" required />
|
|
27
|
+
<v-text-field v-model.trim="form.name" density="comfortable" :rules="[requiredRule]" />
|
|
28
|
+
</v-col>
|
|
29
|
+
</v-row>
|
|
30
|
+
</v-col>
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
<v-col cols="12">
|
|
34
|
+
<v-row>
|
|
35
|
+
<v-col cols="12">
|
|
36
|
+
<InputLabel class="text-capitalize" title="NRIC/Passport/ID No." />
|
|
37
|
+
<InputNRICNumber v-model.trim="form.nric" density="comfortable" />
|
|
38
|
+
</v-col>
|
|
39
|
+
</v-row>
|
|
40
|
+
</v-col>
|
|
41
|
+
|
|
42
|
+
<v-col cols="12">
|
|
43
|
+
<InputLabel class="text-capitalize" title="Phone Number" required />
|
|
44
|
+
<InputPhoneNumberV2 v-model="form.contact" density="comfortable" :rules="[requiredRule]" />
|
|
45
|
+
</v-col>
|
|
46
|
+
|
|
47
|
+
<v-col cols="12">
|
|
48
|
+
<InputLabel class="text-capitalize" title="Vehicle Number" />
|
|
49
|
+
<InputVehicleNumber v-model.trim="form.plateNumber" density="comfortable" />
|
|
50
|
+
</v-col>
|
|
51
|
+
|
|
52
|
+
<v-col cols="12">
|
|
53
|
+
<InputLabel class="text-capitalize" title="Block" required />
|
|
54
|
+
<v-select v-model="form.block" :items="blocksArray" item-value="value" item-title="title" attached
|
|
55
|
+
:loading="blockStatus === 'pending'" @update:model-value="handleChangeBlock" density="comfortable"
|
|
56
|
+
:rules="[requiredRule]" />
|
|
57
|
+
</v-col>
|
|
58
|
+
|
|
59
|
+
<v-col cols="12">
|
|
60
|
+
<InputLabel class="text-capitalize" title="Level" required />
|
|
61
|
+
<v-select v-model="form.level" :items="levelsArray" density="comfortable" :disabled="!form.block"
|
|
62
|
+
:loading="levelsStatus === 'pending'" @update:model-value="handleChangeLevel" :rules="[requiredRule]" />
|
|
63
|
+
</v-col>
|
|
64
|
+
|
|
65
|
+
<v-col cols="12">
|
|
66
|
+
<InputLabel class="text-capitalize" title="Unit" required />
|
|
67
|
+
<v-select v-model="form.unit" :items="unitsArray" @update:model-value="handleUpdateUnit"
|
|
68
|
+
density="comfortable" :disabled="!form.level" :loading="unitsStatus === 'pending'"
|
|
69
|
+
:rules="[requiredRule]" />
|
|
70
|
+
</v-col>
|
|
71
|
+
|
|
72
|
+
<v-col cols="12">
|
|
73
|
+
<InputLabel class="text-capitalize" title="Start Date" required />
|
|
74
|
+
<InputDateTimePicker v-model:utc="form.start" ref="startDateRef" name="start_date" :rules="[validStartDateRule]" />
|
|
75
|
+
</v-col>
|
|
76
|
+
<v-col cols="12">
|
|
77
|
+
<InputLabel class="text-capitalize" title="End Date" required />
|
|
78
|
+
<InputDateTimePicker v-model:utc="form.end" ref="endDateRef" name="end_date" :rules="[validExpiryDateRule]" />
|
|
79
|
+
</v-col>
|
|
80
|
+
|
|
81
|
+
<v-col cols="12">
|
|
82
|
+
<InputLabel class="text-capitalize" title="Remarks" />
|
|
83
|
+
<v-textarea v-model="form.remarks" density="comfortable" :rows="3" no-resize />
|
|
84
|
+
</v-col>
|
|
85
|
+
</template>
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
<template v-else-if="step === 2">
|
|
89
|
+
<v-col cols="12">
|
|
90
|
+
<PassInformation />
|
|
91
|
+
</v-col>
|
|
92
|
+
|
|
93
|
+
<v-col v-if="prop.mode === 'add'" cols="12" class="mt-2">
|
|
94
|
+
<v-checkbox v-model="createMore" density="comfortable" hide-details>
|
|
95
|
+
<template #label>
|
|
96
|
+
<span class="text-subtitle-2 font-weight-bold">
|
|
97
|
+
Create more
|
|
98
|
+
</span>
|
|
99
|
+
</template>
|
|
100
|
+
</v-checkbox>
|
|
101
|
+
</v-col>
|
|
102
|
+
</template>
|
|
103
|
+
|
|
104
|
+
</v-row>
|
|
105
|
+
</v-form>
|
|
106
|
+
</v-card-text>
|
|
107
|
+
|
|
108
|
+
<v-row no-gutters class="w-100" v-if="errorMessage">
|
|
109
|
+
<p class="text-error w-100 text-center text-subtitle-2">{{ errorMessage }}</p>
|
|
110
|
+
</v-row>
|
|
111
|
+
<v-toolbar density="compact">
|
|
112
|
+
<v-row no-gutters>
|
|
113
|
+
<v-col cols="6">
|
|
114
|
+
<v-btn v-if="step > 1" tile block variant="text" class="text-none" size="48" @click="back" text="Back" />
|
|
115
|
+
<v-btn v-else tile block variant="text" class="text-none" size="48" @click="close" text="Close" />
|
|
116
|
+
</v-col>
|
|
117
|
+
<v-col cols="6">
|
|
118
|
+
<v-btn tile block variant="flat" color="primary-button" class="text-none" size="48" :disabled="processing"
|
|
119
|
+
@click="handleNextPage" :text="step === 2 ? 'Submit' : 'Next'" />
|
|
120
|
+
</v-col>
|
|
121
|
+
</v-row>
|
|
122
|
+
</v-toolbar>
|
|
123
|
+
</v-card>
|
|
124
|
+
</template>
|
|
125
|
+
|
|
126
|
+
<script setup lang="ts">
|
|
127
|
+
import { property } from 'zod/v4';
|
|
128
|
+
|
|
129
|
+
const prop = defineProps({
|
|
130
|
+
org: {
|
|
131
|
+
type: String,
|
|
132
|
+
required: true
|
|
133
|
+
},
|
|
134
|
+
site: {
|
|
135
|
+
type: String,
|
|
136
|
+
required: true
|
|
137
|
+
},
|
|
138
|
+
mode: {
|
|
139
|
+
type: String as PropType<'add' | 'edit'>,
|
|
140
|
+
default: 'add'
|
|
141
|
+
},
|
|
142
|
+
type: {
|
|
143
|
+
type: String as PropType<TPeopleType>,
|
|
144
|
+
required: true
|
|
145
|
+
},
|
|
146
|
+
activeId: {
|
|
147
|
+
type: String as PropType<string | null>, // id of person you are trying to update
|
|
148
|
+
default: null
|
|
149
|
+
},
|
|
150
|
+
people: {
|
|
151
|
+
type: Object as PropType<TPeople>,
|
|
152
|
+
required: false
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const { requiredRule, formatDateISO8601 } = useUtils();
|
|
157
|
+
const { getSiteById, getSiteLevels, getSiteUnits } = useSiteSettings();
|
|
158
|
+
const { create, updateById } = usePeople();
|
|
159
|
+
|
|
160
|
+
const emit = defineEmits(['back', 'select', 'done', 'done:more', 'error', 'close']);
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
const form = reactive<Partial<TPeoplePayload>>({
|
|
164
|
+
name: "",
|
|
165
|
+
nric: "",
|
|
166
|
+
contact: "",
|
|
167
|
+
plateNumber: "",
|
|
168
|
+
block: "",
|
|
169
|
+
level: "",
|
|
170
|
+
unit: "",
|
|
171
|
+
unitName: "",
|
|
172
|
+
remarks: "",
|
|
173
|
+
start: "",
|
|
174
|
+
end: "",
|
|
175
|
+
type: prop.type
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
const validForm = ref(false);
|
|
180
|
+
const formRef = ref<HTMLFormElement | null>(null);
|
|
181
|
+
const processing = ref(false);
|
|
182
|
+
const message = ref('');
|
|
183
|
+
const errorMessage = ref('');
|
|
184
|
+
const createMore = ref(false)
|
|
185
|
+
|
|
186
|
+
const startDateRef = ref<HTMLElement>()
|
|
187
|
+
const endDateRef = ref<HTMLElement>()
|
|
188
|
+
|
|
189
|
+
const step = ref(1)
|
|
190
|
+
|
|
191
|
+
const blocksArray = ref<TDefaultOptionObj[]>([]);
|
|
192
|
+
const levelsArray = ref<TDefaultOptionObj[]>([]);
|
|
193
|
+
const unitsArray = ref<TDefaultOptionObj[]>([]);
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
const contractorTypes = [
|
|
198
|
+
{ title: "Estate Contractor", value: "estate-contractor" },
|
|
199
|
+
{ title: "Home Contractor", value: "home-contractor" },
|
|
200
|
+
{ title: "Property Agent", value: "property-agent" },
|
|
201
|
+
{ title: "House Mover", value: "house-mover" },
|
|
202
|
+
]
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
const { data: siteData, refresh: refreshSiteData, status: blockStatus } = useLazyAsyncData(
|
|
207
|
+
`fetch-site-data-${prop.site}`,
|
|
208
|
+
() => getSiteById(prop.site),);
|
|
209
|
+
|
|
210
|
+
const { data: levelsData, refresh: refreshLevelsData, status: levelsStatus } = useLazyAsyncData(
|
|
211
|
+
`fetch-levels-data-${prop.site}-${form.block}`,
|
|
212
|
+
async () => {
|
|
213
|
+
if (!form.block) return Promise.resolve(null);
|
|
214
|
+
return await getSiteLevels(prop.site, { block: Number(form.block) })
|
|
215
|
+
}, { watch: [() => form.block],});
|
|
216
|
+
|
|
217
|
+
const { data: unitsData, refresh: refreshUnitsData, status: unitsStatus } = useLazyAsyncData(
|
|
218
|
+
`fetch-units-data-${prop.site}-${form.level}`,
|
|
219
|
+
async () => {
|
|
220
|
+
if (!form.level) return Promise.resolve(null);
|
|
221
|
+
return await getSiteUnits(prop.site, Number(form.block), form.level)
|
|
222
|
+
}, { watch: [()=> form.level]});
|
|
223
|
+
|
|
224
|
+
watch(
|
|
225
|
+
siteData,
|
|
226
|
+
(newVal) => {
|
|
227
|
+
const siteDataValue = newVal as any;
|
|
228
|
+
if (siteDataValue) {
|
|
229
|
+
const numberOfBlocks = siteDataValue.metadata?.block || 0;
|
|
230
|
+
for (let i = 1; i <= numberOfBlocks; i++) {
|
|
231
|
+
blocksArray.value.push({
|
|
232
|
+
title: `Block ${i}`,
|
|
233
|
+
value: i
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
} else {
|
|
238
|
+
blocksArray.value = [];
|
|
239
|
+
}
|
|
240
|
+
}, { immediate: true });
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
watch(
|
|
244
|
+
levelsData,
|
|
245
|
+
(newVal: any) => {
|
|
246
|
+
if (newVal) {
|
|
247
|
+
const arr = newVal.levels || [];
|
|
248
|
+
levelsArray.value = arr?.map((level: any) => ({
|
|
249
|
+
title: level,
|
|
250
|
+
value: level
|
|
251
|
+
}));
|
|
252
|
+
} else {
|
|
253
|
+
levelsArray.value = [];
|
|
254
|
+
}
|
|
255
|
+
}, { immediate: true });
|
|
256
|
+
|
|
257
|
+
watch(
|
|
258
|
+
unitsData,
|
|
259
|
+
(newVal: any) => {
|
|
260
|
+
if (newVal && Array.isArray(newVal)) {
|
|
261
|
+
const arr = newVal || [];
|
|
262
|
+
unitsArray.value = arr?.map((unit: any) => ({
|
|
263
|
+
title: unit?.name,
|
|
264
|
+
value: unit?._id
|
|
265
|
+
}));
|
|
266
|
+
} else {
|
|
267
|
+
unitsArray.value = [];
|
|
268
|
+
}
|
|
269
|
+
}, { immediate: true });
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
function handleChangeBlock(value: any) {
|
|
274
|
+
form.level = '';
|
|
275
|
+
form.unit = '';
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function handleChangeLevel(value: any) {
|
|
279
|
+
form.unit = '';
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function handleUpdateUnit(value: any) {
|
|
283
|
+
const selectedUnit = unitsArray.value?.find((x: any) => x.value === value)
|
|
284
|
+
form.unitName = selectedUnit?.title || ''
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function dateToday() {
|
|
288
|
+
const today = new Date()
|
|
289
|
+
return today.toISOString()
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
function back() {
|
|
294
|
+
if (step.value > 1) {
|
|
295
|
+
step.value--
|
|
296
|
+
}
|
|
297
|
+
emit("back");
|
|
298
|
+
message.value = '';
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function close() {
|
|
302
|
+
emit("close");
|
|
303
|
+
message.value = '';
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
async function handleNextPage() {
|
|
309
|
+
errorMessage.value = ''
|
|
310
|
+
formRef.value!.validate()
|
|
311
|
+
if (!validForm.value) {
|
|
312
|
+
errorMessage.value = "Please complete all required fields *"
|
|
313
|
+
return
|
|
314
|
+
}
|
|
315
|
+
const stepVal = step.value
|
|
316
|
+
if (stepVal < 2) step.value++
|
|
317
|
+
else if (stepVal === 2) {
|
|
318
|
+
await submit()
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function resetForm() {
|
|
324
|
+
formRef.value?.resetValidation();
|
|
325
|
+
|
|
326
|
+
Object.assign(form, {
|
|
327
|
+
name: "",
|
|
328
|
+
nric: "",
|
|
329
|
+
contact: "",
|
|
330
|
+
plateNumber: "",
|
|
331
|
+
block: "",
|
|
332
|
+
level: "",
|
|
333
|
+
unit: "",
|
|
334
|
+
remarks: ""
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
validForm.value = false;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function validStartDateRule(value: string) {
|
|
341
|
+
if (!value) {
|
|
342
|
+
return 'Start Date is required';
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function validExpiryDateRule(value: string) {
|
|
349
|
+
const startDateISO = form.start;
|
|
350
|
+
if (!value) {
|
|
351
|
+
return 'End Date is required';
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (value && startDateISO) {
|
|
355
|
+
const expiry = new Date(value);
|
|
356
|
+
const start = new Date(startDateISO as string);
|
|
357
|
+
return expiry > start || 'End date must be later than start date';
|
|
358
|
+
}
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
async function submit() {
|
|
365
|
+
formRef.value!.validate()
|
|
366
|
+
errorMessage.value = '';
|
|
367
|
+
processing.value = true;
|
|
368
|
+
|
|
369
|
+
let payload: Partial<TPeoplePayload> = {}
|
|
370
|
+
|
|
371
|
+
if (prop.mode === 'add') {
|
|
372
|
+
payload = {
|
|
373
|
+
...payload,
|
|
374
|
+
org: prop.org,
|
|
375
|
+
site: prop.site,
|
|
376
|
+
...form,
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
} else if (prop.mode === 'edit') {
|
|
380
|
+
const { type, ...rest } = form
|
|
381
|
+
payload = {
|
|
382
|
+
...payload,
|
|
383
|
+
...rest
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
}
|
|
387
|
+
try {
|
|
388
|
+
if (prop.mode === 'add') {
|
|
389
|
+
await create(payload)
|
|
390
|
+
} else if (prop.mode === 'edit'){
|
|
391
|
+
const userId = prop.activeId
|
|
392
|
+
if(!userId) {
|
|
393
|
+
throw new Error('User Id prop is not defined')
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
await updateById(userId, payload)
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (createMore.value) {
|
|
400
|
+
resetForm()
|
|
401
|
+
step.value = 1;
|
|
402
|
+
errorMessage.value = ""
|
|
403
|
+
emit("done:more")
|
|
404
|
+
createMore.value = false
|
|
405
|
+
} else emit("done")
|
|
406
|
+
|
|
407
|
+
} catch (error: any) {
|
|
408
|
+
const err = error?.data?.message
|
|
409
|
+
errorMessage.value = err || `Failed to ${prop.mode === 'add' ? 'add' : 'update'} ${prop.type}. Please try again.`;
|
|
410
|
+
} finally {
|
|
411
|
+
processing.value = false;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
watch(() => form.plateNumber, (newVal) => {
|
|
416
|
+
form.plateNumber = newVal?.toUpperCase()
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
watch(() => [form.end, form.start], () => {
|
|
420
|
+
(endDateRef.value as any)?.validate();
|
|
421
|
+
(startDateRef.value as any)?.validate();
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
async function mountExistingData() {
|
|
425
|
+
setTimeout(() => {
|
|
426
|
+
const people = prop.people
|
|
427
|
+
if (!people) return
|
|
428
|
+
|
|
429
|
+
(Object.keys(form) as (keyof typeof form)[]).forEach((key) => {
|
|
430
|
+
if (key in people) {
|
|
431
|
+
form[key] = people[key as keyof TPeople]
|
|
432
|
+
}
|
|
433
|
+
})
|
|
434
|
+
}, 100)
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
onMounted(() => {
|
|
438
|
+
step.value = 1;
|
|
439
|
+
createMore.value = false;
|
|
440
|
+
if(prop.mode === 'edit'){
|
|
441
|
+
mountExistingData()
|
|
442
|
+
} else {
|
|
443
|
+
form.start = dateToday()
|
|
444
|
+
}
|
|
445
|
+
})
|
|
446
|
+
|
|
447
|
+
</script>
|
|
448
|
+
<style scoped>
|
|
449
|
+
.button-outline-class {
|
|
450
|
+
border: 1px solid rgba(var(--v-theme-primary));
|
|
451
|
+
}
|
|
452
|
+
</style>
|
package/components/TableMain.vue
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<v-row no-gutters>
|
|
3
3
|
<!-- Top Actions -->
|
|
4
|
-
<v-col cols="12" class="mb-2" v-if="canCreate || $slots.actions">
|
|
4
|
+
<v-col cols="12" class="mb-2" v-if="(canCreate || $slots.actions)">
|
|
5
5
|
<v-row no-gutters>
|
|
6
6
|
<slot name="actions">
|
|
7
7
|
<v-btn v-if="canCreate" class="text-none" rounded="pill" variant="tonal" size="large"
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
<v-btn fab icon density="comfortable" @click="emits('refresh')">
|
|
22
22
|
<v-icon>mdi-refresh</v-icon>
|
|
23
23
|
</v-btn>
|
|
24
|
+
<slot name="prepend-additional" />
|
|
24
25
|
</template>
|
|
25
26
|
|
|
26
27
|
<template #append>
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-card width="100%">
|
|
3
|
+
<v-toolbar>
|
|
4
|
+
<template v-if="$slots.header">
|
|
5
|
+
<slot name="header" :title="title" :onClose="onClose" />
|
|
6
|
+
</template>
|
|
7
|
+
<template v-else>
|
|
8
|
+
<v-row no-gutters class="fill-height px-6" align="center">
|
|
9
|
+
<span class="font-weight-bold text-h5 text-capitalize">{{
|
|
10
|
+
title
|
|
11
|
+
}}</span>
|
|
12
|
+
</v-row>
|
|
13
|
+
</template>
|
|
14
|
+
</v-toolbar>
|
|
15
|
+
|
|
16
|
+
<v-card-text style="max-height: 100vh; overflow-y: auto" class="pb-0">
|
|
17
|
+
<slot name="content" />
|
|
18
|
+
</v-card-text>
|
|
19
|
+
|
|
20
|
+
<v-toolbar class="pa-0" density="compact">
|
|
21
|
+
<v-row no-gutters>
|
|
22
|
+
<v-col :cols="canDelete || canUpdate ? 6 : 12" class="pa-0">
|
|
23
|
+
<v-btn block variant="text" class="text-none" size="large" @click="emit('close')" height="48">
|
|
24
|
+
Close
|
|
25
|
+
</v-btn>
|
|
26
|
+
</v-col>
|
|
27
|
+
|
|
28
|
+
<v-col cols="6" class="pa-0" v-if="canDelete || canUpdate">
|
|
29
|
+
<v-menu>
|
|
30
|
+
<template #activator="{ props }">
|
|
31
|
+
<v-btn block variant="flat" color="black" class="text-none" height="48" v-bind="props" tile>
|
|
32
|
+
More actions
|
|
33
|
+
</v-btn>
|
|
34
|
+
</template>
|
|
35
|
+
|
|
36
|
+
<v-list class="pa-0">
|
|
37
|
+
<v-list-item v-if="canUpdate" @click="emit('edit')">
|
|
38
|
+
<v-list-item-title class="text-subtitle-2">
|
|
39
|
+
{{ editButtonLabel }}
|
|
40
|
+
</v-list-item-title>
|
|
41
|
+
</v-list-item>
|
|
42
|
+
|
|
43
|
+
<v-list-item v-if="canDelete" @click="emit('delete')" class="text-red">
|
|
44
|
+
<v-list-item-title class="text-subtitle-2">
|
|
45
|
+
{{ deleteButtonLabel }}
|
|
46
|
+
</v-list-item-title>
|
|
47
|
+
</v-list-item>
|
|
48
|
+
</v-list>
|
|
49
|
+
</v-menu>
|
|
50
|
+
</v-col>
|
|
51
|
+
</v-row>
|
|
52
|
+
</v-toolbar>
|
|
53
|
+
</v-card>
|
|
54
|
+
</template>
|
|
55
|
+
|
|
56
|
+
<script setup lang="ts">
|
|
57
|
+
const prop = defineProps({
|
|
58
|
+
canUpdate: {
|
|
59
|
+
type: Boolean,
|
|
60
|
+
default: true,
|
|
61
|
+
},
|
|
62
|
+
canDelete: {
|
|
63
|
+
type: Boolean,
|
|
64
|
+
default: true,
|
|
65
|
+
},
|
|
66
|
+
editButtonLabel: {
|
|
67
|
+
type: String,
|
|
68
|
+
default: "Edit",
|
|
69
|
+
},
|
|
70
|
+
deleteButtonLabel: {
|
|
71
|
+
type: String,
|
|
72
|
+
default: "Delete",
|
|
73
|
+
},
|
|
74
|
+
title: {
|
|
75
|
+
type: String,
|
|
76
|
+
default: "Details",
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const emit = defineEmits(["close", "edit", "delete"]);
|
|
81
|
+
const { canUpdate, editButtonLabel, deleteButtonLabel, title } = prop;
|
|
82
|
+
</script>
|
|
83
|
+
|
|
84
|
+
<style scoped></style>
|