@iservice365/layer-common 0.1.0 → 0.2.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/.playground/app.vue +7 -2
- package/.playground/pages/feedback.vue +30 -0
- package/CHANGELOG.md +12 -0
- package/components/Chat/Bubbles.vue +53 -0
- package/components/Chat/Information.vue +187 -0
- package/components/Chat/ListCard.vue +62 -0
- package/components/Chat/Message.vue +149 -0
- package/components/Chat/Navigation.vue +150 -0
- package/components/ConfirmDialog.vue +66 -0
- package/components/Container/Standard.vue +33 -0
- package/components/Feedback/Form.vue +136 -0
- package/components/FeedbackDetail.vue +465 -0
- package/components/FeedbackMain.vue +454 -0
- package/components/FormDialog.vue +65 -0
- package/components/Input/File.vue +203 -0
- package/components/Input/ListGroupSelection.vue +96 -0
- package/components/Input/NewDate.vue +123 -0
- package/components/Input/Number.vue +124 -0
- package/components/InvitationMain.vue +284 -0
- package/components/Layout/Header.vue +14 -4
- package/components/ListView.vue +87 -0
- package/components/MemberMain.vue +459 -0
- package/components/RolePermissionFormCreate.vue +161 -0
- package/components/RolePermissionFormPreviewUpdate.vue +183 -0
- package/components/RolePermissionMain.vue +361 -0
- package/components/ServiceProviderFormCreate.vue +154 -0
- package/components/ServiceProviderMain.vue +195 -0
- package/components/SignaturePad.vue +73 -0
- package/components/SpecificAttr.vue +53 -0
- package/components/SwitchContext.vue +26 -5
- package/components/TableList.vue +150 -0
- package/components/TableListSecondary.vue +164 -0
- package/components/WorkOrder/Create.vue +197 -0
- package/components/WorkOrder/ListView.vue +96 -0
- package/components/WorkOrder/Main.vue +308 -0
- package/components/Workorder.vue +1 -0
- package/composables/useAddress.ts +107 -0
- package/composables/useCommonPermission.ts +130 -0
- package/composables/useCustomer.ts +113 -0
- package/composables/useFeedback.ts +117 -0
- package/composables/useFile.ts +40 -0
- package/composables/useInvoice.ts +18 -0
- package/composables/useLocal.ts +24 -4
- package/composables/useLocalAuth.ts +62 -20
- package/composables/useLocalSetup.ts +13 -0
- package/composables/useMember.ts +111 -0
- package/composables/useOrg.ts +76 -92
- package/composables/usePaymentMethod.ts +101 -0
- package/composables/usePrice.ts +15 -0
- package/composables/usePromoCode.ts +36 -0
- package/composables/useRole.ts +38 -7
- package/composables/useServiceProvider.ts +218 -0
- package/composables/useSite.ts +108 -0
- package/composables/useSubscription.ts +149 -0
- package/composables/useUser.ts +38 -14
- package/composables/useUtils.ts +218 -6
- package/composables/useVerification.ts +33 -0
- package/composables/useWorkOrder.ts +68 -0
- package/middleware/01.auth.ts +11 -0
- package/middleware/02.org.ts +18 -0
- package/middleware/03.customer.ts +13 -0
- package/middleware/member.ts +4 -0
- package/nuxt.config.ts +3 -1
- package/package.json +7 -3
- package/pages/index.vue +3 -0
- package/pages/payment-method-linked.vue +31 -0
- package/pages/require-customer.vue +56 -0
- package/pages/require-organization-membership.vue +47 -0
- package/pages/unauthorized.vue +29 -0
- package/plugins/API.ts +2 -25
- package/plugins/iconify.client.ts +5 -0
- package/plugins/secure-member.client.ts +54 -0
- package/plugins/vuetify.ts +2 -0
- package/public/bg-camera.jpg +0 -0
- package/public/bg-city.jpg +0 -0
- package/public/bg-condo.jpg +0 -0
- package/public/images/icons/delete-icon.png +0 -0
- package/public/sprite.svg +1 -0
- package/types/address.d.ts +13 -0
- package/types/customer.d.ts +15 -0
- package/types/feedback.d.ts +63 -0
- package/types/local.d.ts +47 -38
- package/types/member.d.ts +21 -0
- package/types/org.d.ts +13 -0
- package/types/permission.d.ts +1 -0
- package/types/price.d.ts +17 -0
- package/types/promo-code.d.ts +19 -0
- package/types/service-provider.d.ts +15 -0
- package/types/site.d.ts +13 -0
- package/types/subscription.d.ts +23 -0
- package/types/user.d.ts +19 -0
- package/types/verification.d.ts +20 -0
- package/types/work-order.d.ts +40 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-input v-bind="attrs">
|
|
3
|
+
<v-card width="100%" v-bind="attrs">
|
|
4
|
+
<v-list
|
|
5
|
+
v-model:selected="selected"
|
|
6
|
+
lines="two"
|
|
7
|
+
:select-strategy="attrs.readonly ? 'classic' : 'leaf'"
|
|
8
|
+
class="pa-0"
|
|
9
|
+
density="compact"
|
|
10
|
+
read-only
|
|
11
|
+
open-strategy="single"
|
|
12
|
+
>
|
|
13
|
+
<template
|
|
14
|
+
v-for="(permission, permissionKey, permissionIndex) in props.items"
|
|
15
|
+
:key="permissionKey"
|
|
16
|
+
>
|
|
17
|
+
<v-divider v-if="permissionIndex > 0"></v-divider>
|
|
18
|
+
<v-list-group :value="permissionKey" fluid>
|
|
19
|
+
<template v-slot:activator="{ props }">
|
|
20
|
+
<v-list-item v-bind="props" density="compact">
|
|
21
|
+
<span class="text-capitalize">
|
|
22
|
+
{{ String(permissionKey).replace(/-/g, " ") }}
|
|
23
|
+
</span>
|
|
24
|
+
|
|
25
|
+
<template #prepend>
|
|
26
|
+
<v-chip class="mr-2" small>
|
|
27
|
+
{{ selectedActionCount(String(permissionKey)) }}
|
|
28
|
+
</v-chip>
|
|
29
|
+
</template>
|
|
30
|
+
</v-list-item>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<template v-for="(item, itemKey) in permission" :key="itemKey">
|
|
34
|
+
<v-divider></v-divider>
|
|
35
|
+
<v-list-item v-if="attrs.readonly" density="compact">
|
|
36
|
+
<template #title class="pl-2">
|
|
37
|
+
<span class="text-subtitle-2 text-capitalize">
|
|
38
|
+
{{ String(itemKey).replace(/-/g, " ") }}
|
|
39
|
+
</span>
|
|
40
|
+
</template>
|
|
41
|
+
|
|
42
|
+
<template #subtitle class="pl-2">
|
|
43
|
+
<span class="text-subtitle-2">{{ item.description }}</span>
|
|
44
|
+
</template>
|
|
45
|
+
</v-list-item>
|
|
46
|
+
|
|
47
|
+
<v-list-item
|
|
48
|
+
v-else
|
|
49
|
+
:value="`${permissionKey}:${itemKey}`"
|
|
50
|
+
density="compact"
|
|
51
|
+
>
|
|
52
|
+
<template #title class="pl-2">
|
|
53
|
+
<span class="text-subtitle-2 text-capitalize">
|
|
54
|
+
{{ String(itemKey).replace(/-/g, " ") }}
|
|
55
|
+
</span>
|
|
56
|
+
</template>
|
|
57
|
+
|
|
58
|
+
<template #subtitle class="pl-2">
|
|
59
|
+
<span class="text-subtitle-2 text-capitalize">
|
|
60
|
+
{{ String(item.description).replace(/-/g, " ") }}
|
|
61
|
+
</span>
|
|
62
|
+
</template>
|
|
63
|
+
|
|
64
|
+
<template #prepend="{ isSelected, select }" class="pl-1">
|
|
65
|
+
<v-list-item-action start>
|
|
66
|
+
<v-checkbox-btn
|
|
67
|
+
:model-value="isSelected"
|
|
68
|
+
@update:model-value="select"
|
|
69
|
+
></v-checkbox-btn>
|
|
70
|
+
</v-list-item-action>
|
|
71
|
+
</template>
|
|
72
|
+
</v-list-item>
|
|
73
|
+
</template>
|
|
74
|
+
</v-list-group>
|
|
75
|
+
</template>
|
|
76
|
+
</v-list>
|
|
77
|
+
</v-card>
|
|
78
|
+
</v-input>
|
|
79
|
+
</template>
|
|
80
|
+
|
|
81
|
+
<script setup lang="ts">
|
|
82
|
+
const selected = defineModel<Array<string>>({ default: [] });
|
|
83
|
+
const attrs = useAttrs();
|
|
84
|
+
const props = defineProps({
|
|
85
|
+
items: {
|
|
86
|
+
type: Object,
|
|
87
|
+
required: true,
|
|
88
|
+
default: () => ({}),
|
|
89
|
+
},
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const selectedActionCount = (resource: string) => {
|
|
93
|
+
return selected.value.filter((permission) => permission.startsWith(resource))
|
|
94
|
+
.length;
|
|
95
|
+
};
|
|
96
|
+
</script>
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { defineModel, defineProps, computed, ref } from "vue";
|
|
3
|
+
|
|
4
|
+
const props = defineProps<{
|
|
5
|
+
rules?: ((value: string) => boolean | string)[]; // Accepts external validation rules
|
|
6
|
+
}>();
|
|
7
|
+
|
|
8
|
+
const dateValue = defineModel<string>({ default: "" });
|
|
9
|
+
const inputRef = ref<HTMLInputElement | null>(null);
|
|
10
|
+
|
|
11
|
+
const formatDate = (event: Event) => {
|
|
12
|
+
const input = event.target as HTMLInputElement;
|
|
13
|
+
let value = input.value.replace(/\D/g, ""); // Remove non-numeric characters
|
|
14
|
+
|
|
15
|
+
// Format as MM/DD/YYYY
|
|
16
|
+
let formattedValue = value
|
|
17
|
+
.slice(0, 8)
|
|
18
|
+
.replace(/(\d{2})(\d{0,2})?(\d{0,4})?/, (_, m, d, y) =>
|
|
19
|
+
[m, d, y].filter(Boolean).join("/")
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
// Preserve cursor position
|
|
23
|
+
const cursorPosition = input.selectionStart ?? 0;
|
|
24
|
+
const slashCountBefore = (dateValue.value.match(/\//g) || []).length;
|
|
25
|
+
const slashCountAfter = (formattedValue.match(/\//g) || []).length;
|
|
26
|
+
const cursorOffset = slashCountAfter - slashCountBefore;
|
|
27
|
+
|
|
28
|
+
// Only update if value changed to prevent unnecessary reactivity updates
|
|
29
|
+
if (dateValue.value !== formattedValue) {
|
|
30
|
+
dateValue.value = formattedValue;
|
|
31
|
+
setTimeout(() => {
|
|
32
|
+
input.setSelectionRange(
|
|
33
|
+
cursorPosition + cursorOffset,
|
|
34
|
+
cursorPosition + cursorOffset
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Compute combined validation rules
|
|
41
|
+
const computedRules = computed(() => {
|
|
42
|
+
return props.rules ? [...props.rules] : [];
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Handle arrow key increments with cursor preservation
|
|
46
|
+
const handleArrowKeys = (event: KeyboardEvent) => {
|
|
47
|
+
if (!dateValue.value) return;
|
|
48
|
+
|
|
49
|
+
const input = event.target as HTMLInputElement;
|
|
50
|
+
const cursorPosition = input.selectionStart ?? 0; // Store cursor position
|
|
51
|
+
dateValue.value.split("/").map(Number);
|
|
52
|
+
|
|
53
|
+
let updatedDate = dateValue.value;
|
|
54
|
+
|
|
55
|
+
// Determine which part to modify
|
|
56
|
+
if (cursorPosition <= 2) {
|
|
57
|
+
updatedDate = modifyDatePart(
|
|
58
|
+
dateValue.value,
|
|
59
|
+
"month",
|
|
60
|
+
event.key === "ArrowUp" ? 1 : -1
|
|
61
|
+
);
|
|
62
|
+
} else if (cursorPosition <= 5) {
|
|
63
|
+
updatedDate = modifyDatePart(
|
|
64
|
+
dateValue.value,
|
|
65
|
+
"day",
|
|
66
|
+
event.key === "ArrowUp" ? 1 : -1
|
|
67
|
+
);
|
|
68
|
+
} else {
|
|
69
|
+
updatedDate = modifyDatePart(
|
|
70
|
+
dateValue.value,
|
|
71
|
+
"year",
|
|
72
|
+
event.key === "ArrowUp" ? 1 : -1
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (dateValue.value !== updatedDate) {
|
|
77
|
+
dateValue.value = updatedDate;
|
|
78
|
+
setTimeout(() => {
|
|
79
|
+
input.setSelectionRange(cursorPosition, cursorPosition);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
event.preventDefault();
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const modifyDatePart = (
|
|
87
|
+
date: string,
|
|
88
|
+
part: "month" | "day" | "year",
|
|
89
|
+
change: number
|
|
90
|
+
) => {
|
|
91
|
+
let [month, day, year] = date.split("/").map(Number);
|
|
92
|
+
|
|
93
|
+
if (part === "month") {
|
|
94
|
+
month = Math.max(1, Math.min(12, month + change));
|
|
95
|
+
const maxDays = new Date(year, month, 0).getDate();
|
|
96
|
+
day = Math.min(day, maxDays); // Adjust day to fit new month's max days
|
|
97
|
+
} else if (part === "day") {
|
|
98
|
+
const maxDays = new Date(year, month, 0).getDate();
|
|
99
|
+
day = Math.max(1, Math.min(maxDays, day + change));
|
|
100
|
+
} else if (part === "year") {
|
|
101
|
+
year += change;
|
|
102
|
+
const maxDays = new Date(year, month, 0).getDate();
|
|
103
|
+
day = Math.min(day, maxDays); // Adjust day to fit new year's month
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return `${String(month).padStart(2, "0")}/${String(day).padStart(
|
|
107
|
+
2,
|
|
108
|
+
"0"
|
|
109
|
+
)}/${year}`;
|
|
110
|
+
};
|
|
111
|
+
</script>
|
|
112
|
+
|
|
113
|
+
<template>
|
|
114
|
+
<v-text-field
|
|
115
|
+
ref="inputRef"
|
|
116
|
+
v-model="dateValue"
|
|
117
|
+
placeholder="MM/DD/YYYY"
|
|
118
|
+
@input="formatDate"
|
|
119
|
+
@keydown.up="handleArrowKeys"
|
|
120
|
+
@keydown.down="handleArrowKeys"
|
|
121
|
+
:rules="computedRules"
|
|
122
|
+
></v-text-field>
|
|
123
|
+
</template>
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, computed, nextTick, useAttrs } from "vue";
|
|
3
|
+
|
|
4
|
+
const props = defineProps({
|
|
5
|
+
infinityEnabled: { type: Boolean, default: false }, // Enable ∞ display for 0
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
const modelValue = defineModel<number>(); // Store actual number
|
|
9
|
+
const inputRef = ref<HTMLInputElement | null>(null);
|
|
10
|
+
const attrs = useAttrs();
|
|
11
|
+
|
|
12
|
+
let cursorPosition = 0;
|
|
13
|
+
let forceCursorToEnd = false;
|
|
14
|
+
|
|
15
|
+
// Computed property to format value with commas or infinity sign
|
|
16
|
+
const formattedValue = computed({
|
|
17
|
+
get: () => {
|
|
18
|
+
if (props.infinityEnabled && modelValue.value === 0) return "∞"; // Show ∞ if enabled
|
|
19
|
+
return modelValue.value?.toLocaleString() ?? "";
|
|
20
|
+
},
|
|
21
|
+
set: (val: string) => {
|
|
22
|
+
if (props.infinityEnabled && val === "∞") {
|
|
23
|
+
modelValue.value = 0; // Convert back to 0 internally
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const rawValue = val.replace(/\D/g, ""); // Remove non-numeric characters
|
|
28
|
+
const numericValue = rawValue ? Number(rawValue) : 0;
|
|
29
|
+
|
|
30
|
+
if (!isNaN(numericValue)) {
|
|
31
|
+
modelValue.value = numericValue;
|
|
32
|
+
nextTick(() => restoreCursor());
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Handle keydown for navigation & number changes
|
|
38
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
39
|
+
if (!inputRef.value || modelValue.value === undefined) return;
|
|
40
|
+
|
|
41
|
+
const { selectionStart, selectionEnd, value } = inputRef.value;
|
|
42
|
+
const isAllSelected = selectionStart === 0 && selectionEnd === value.length;
|
|
43
|
+
|
|
44
|
+
if (
|
|
45
|
+
!/^\d$/.test(event.key) &&
|
|
46
|
+
![
|
|
47
|
+
"Backspace",
|
|
48
|
+
"Delete",
|
|
49
|
+
"ArrowLeft",
|
|
50
|
+
"ArrowRight",
|
|
51
|
+
"ArrowUp",
|
|
52
|
+
"ArrowDown",
|
|
53
|
+
].includes(event.key)
|
|
54
|
+
) {
|
|
55
|
+
event.preventDefault();
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (isAllSelected && /^\d$/.test(event.key)) {
|
|
60
|
+
event.preventDefault();
|
|
61
|
+
modelValue.value = Number(event.key);
|
|
62
|
+
forceCursorToEnd = true;
|
|
63
|
+
nextTick(() => restoreCursor());
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (event.key === "ArrowLeft" || event.key === "ArrowRight") {
|
|
68
|
+
forceCursorToEnd = false;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (event.key === "ArrowUp") {
|
|
73
|
+
event.preventDefault();
|
|
74
|
+
modelValue.value += 1;
|
|
75
|
+
forceCursorToEnd = true;
|
|
76
|
+
} else if (event.key === "ArrowDown") {
|
|
77
|
+
event.preventDefault();
|
|
78
|
+
modelValue.value = Math.max(0, modelValue.value - 1);
|
|
79
|
+
forceCursorToEnd = true;
|
|
80
|
+
} else {
|
|
81
|
+
cursorPosition = selectionStart || 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
nextTick(() => restoreCursor());
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Restore cursor position
|
|
88
|
+
const restoreCursor = () => {
|
|
89
|
+
if (!inputRef.value) return;
|
|
90
|
+
const length = formattedValue.value.length;
|
|
91
|
+
|
|
92
|
+
if (forceCursorToEnd) {
|
|
93
|
+
inputRef.value.setSelectionRange(length, length);
|
|
94
|
+
} else {
|
|
95
|
+
inputRef.value.setSelectionRange(cursorPosition, cursorPosition);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
</script>
|
|
99
|
+
|
|
100
|
+
<template>
|
|
101
|
+
<v-text-field
|
|
102
|
+
ref="inputRef"
|
|
103
|
+
v-model="formattedValue"
|
|
104
|
+
v-bind="attrs"
|
|
105
|
+
type="text"
|
|
106
|
+
@keydown="handleKeyDown"
|
|
107
|
+
>
|
|
108
|
+
<template v-if="$slots.prepend" v-slot:prepend>
|
|
109
|
+
<slot name="prepend"></slot>
|
|
110
|
+
</template>
|
|
111
|
+
|
|
112
|
+
<template v-if="$slots.append" v-slot:append>
|
|
113
|
+
<slot name="append"></slot>
|
|
114
|
+
</template>
|
|
115
|
+
|
|
116
|
+
<template v-if="$slots['append-inner']" v-slot:append-inner>
|
|
117
|
+
<slot name="append-inner"></slot>
|
|
118
|
+
</template>
|
|
119
|
+
|
|
120
|
+
<template v-if="$slots['prepend-inner']" v-slot:prepend-inner>
|
|
121
|
+
<slot name="prepend-inner"></slot>
|
|
122
|
+
</template>
|
|
123
|
+
</v-text-field>
|
|
124
|
+
</template>
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-row no-gutters>
|
|
3
|
+
<v-col cols="12" class="mb-2">
|
|
4
|
+
<v-row no-gutters>
|
|
5
|
+
<v-btn
|
|
6
|
+
class="text-none mr-2"
|
|
7
|
+
rounded="pill"
|
|
8
|
+
variant="tonal"
|
|
9
|
+
:to="{
|
|
10
|
+
name: 'org-customer-site-invitations-invite',
|
|
11
|
+
}"
|
|
12
|
+
size="large"
|
|
13
|
+
v-if="props.inviteMember"
|
|
14
|
+
>
|
|
15
|
+
Invite member
|
|
16
|
+
</v-btn>
|
|
17
|
+
</v-row>
|
|
18
|
+
</v-col>
|
|
19
|
+
<v-col cols="12">
|
|
20
|
+
<v-card width="100%" variant="outlined" border="thin" rounded="lg">
|
|
21
|
+
<v-toolbar density="compact" color="grey-lighten-4">
|
|
22
|
+
<template #prepend>
|
|
23
|
+
<v-btn
|
|
24
|
+
fab
|
|
25
|
+
icon
|
|
26
|
+
density="comfortable"
|
|
27
|
+
@click="_getVerifications({ search: headerSearch, status })"
|
|
28
|
+
>
|
|
29
|
+
<v-icon>mdi-refresh</v-icon>
|
|
30
|
+
</v-btn>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<template #append>
|
|
34
|
+
<v-row no-gutters justify="end" align="center">
|
|
35
|
+
<span class="mr-2 text-caption text-fontgray">
|
|
36
|
+
{{ pageRange }}
|
|
37
|
+
</span>
|
|
38
|
+
<local-pagination
|
|
39
|
+
v-model="page"
|
|
40
|
+
:length="pages"
|
|
41
|
+
@update:value="_getVerifications({ search: headerSearch })"
|
|
42
|
+
/>
|
|
43
|
+
</v-row>
|
|
44
|
+
</template>
|
|
45
|
+
|
|
46
|
+
<template #extension>
|
|
47
|
+
<v-tabs>
|
|
48
|
+
<v-tab
|
|
49
|
+
:to="{
|
|
50
|
+
name: 'org-customer-site-invitations-status-status',
|
|
51
|
+
params: { status: 'pending' },
|
|
52
|
+
}"
|
|
53
|
+
>
|
|
54
|
+
Pending
|
|
55
|
+
</v-tab>
|
|
56
|
+
<v-tab
|
|
57
|
+
:to="{
|
|
58
|
+
name: 'org-customer-site-invitations-status-status',
|
|
59
|
+
params: { status: 'expired' },
|
|
60
|
+
}"
|
|
61
|
+
>
|
|
62
|
+
Expired
|
|
63
|
+
</v-tab>
|
|
64
|
+
</v-tabs>
|
|
65
|
+
</template>
|
|
66
|
+
</v-toolbar>
|
|
67
|
+
|
|
68
|
+
<v-data-table
|
|
69
|
+
:headers="headers"
|
|
70
|
+
:items="items"
|
|
71
|
+
item-value="_id"
|
|
72
|
+
items-per-page="20"
|
|
73
|
+
fixed-header
|
|
74
|
+
hide-default-footer
|
|
75
|
+
style="max-height: calc(100vh - (126px))"
|
|
76
|
+
:loading="loading"
|
|
77
|
+
>
|
|
78
|
+
<template #item.permissions="{ value }">
|
|
79
|
+
<span class="text-caption font-weight-bold text-capitalize">
|
|
80
|
+
permissions
|
|
81
|
+
</span>
|
|
82
|
+
<v-chip>{{ value.length }}</v-chip>
|
|
83
|
+
</template>
|
|
84
|
+
<template #item.createdAt="{ item }">
|
|
85
|
+
{{ formatDate(item.createdAt) }}
|
|
86
|
+
</template>
|
|
87
|
+
<template #item.action-table="{ item }">
|
|
88
|
+
<v-menu
|
|
89
|
+
:close-on-content-click="false"
|
|
90
|
+
offset-y
|
|
91
|
+
width="150"
|
|
92
|
+
v-if="props.cancelInvitation"
|
|
93
|
+
>
|
|
94
|
+
<template v-slot:activator="{ props }">
|
|
95
|
+
<v-icon v-bind="props">mdi-dots-horizontal</v-icon>
|
|
96
|
+
</template>
|
|
97
|
+
<v-list>
|
|
98
|
+
<v-list-item @click="openConfirmDialog(item._id)">
|
|
99
|
+
Cancel Invite
|
|
100
|
+
</v-list-item>
|
|
101
|
+
</v-list>
|
|
102
|
+
</v-menu>
|
|
103
|
+
</template>
|
|
104
|
+
</v-data-table>
|
|
105
|
+
</v-card>
|
|
106
|
+
</v-col>
|
|
107
|
+
</v-row>
|
|
108
|
+
<ConfirmDialog
|
|
109
|
+
v-model="confirmDialog"
|
|
110
|
+
:loading="cancelLoading"
|
|
111
|
+
@submit="onConfirmCancel"
|
|
112
|
+
>
|
|
113
|
+
<template #title>
|
|
114
|
+
<span class="font-weight-medium text-h5">Cancel Invitation</span>
|
|
115
|
+
</template>
|
|
116
|
+
<template #description>
|
|
117
|
+
<p class="text-subtitle-2">
|
|
118
|
+
Are you sure you want to cancel this invitation? This action cannot be
|
|
119
|
+
undone.
|
|
120
|
+
</p>
|
|
121
|
+
</template>
|
|
122
|
+
<template #footer>
|
|
123
|
+
<v-btn
|
|
124
|
+
variant="text"
|
|
125
|
+
@click="confirmDialog = false"
|
|
126
|
+
:disabled="cancelLoading"
|
|
127
|
+
>
|
|
128
|
+
Close
|
|
129
|
+
</v-btn>
|
|
130
|
+
<v-btn
|
|
131
|
+
color="primary"
|
|
132
|
+
variant="flat"
|
|
133
|
+
@click="onConfirmCancel"
|
|
134
|
+
:loading="cancelLoading"
|
|
135
|
+
>
|
|
136
|
+
Cancel Invite
|
|
137
|
+
</v-btn>
|
|
138
|
+
</template>
|
|
139
|
+
</ConfirmDialog>
|
|
140
|
+
<Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
|
|
141
|
+
</template>
|
|
142
|
+
|
|
143
|
+
<script setup lang="ts">
|
|
144
|
+
const props = defineProps({
|
|
145
|
+
route: {
|
|
146
|
+
type: String,
|
|
147
|
+
default: "index",
|
|
148
|
+
},
|
|
149
|
+
orgId: {
|
|
150
|
+
type: String,
|
|
151
|
+
default: "",
|
|
152
|
+
},
|
|
153
|
+
customerId: {
|
|
154
|
+
type: String,
|
|
155
|
+
default: "",
|
|
156
|
+
},
|
|
157
|
+
siteId: {
|
|
158
|
+
type: String,
|
|
159
|
+
default: "",
|
|
160
|
+
},
|
|
161
|
+
status: {
|
|
162
|
+
type: String,
|
|
163
|
+
default: "pending",
|
|
164
|
+
},
|
|
165
|
+
cancelInvitation: {
|
|
166
|
+
type: Boolean,
|
|
167
|
+
default: true,
|
|
168
|
+
},
|
|
169
|
+
inviteMember: {
|
|
170
|
+
type: Boolean,
|
|
171
|
+
default: true,
|
|
172
|
+
},
|
|
173
|
+
viewInvitations: {
|
|
174
|
+
type: Boolean,
|
|
175
|
+
default: true,
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const { authenticate } = useLocalAuth();
|
|
180
|
+
authenticate();
|
|
181
|
+
|
|
182
|
+
const organization = (useRoute().params.org as string) ?? "";
|
|
183
|
+
|
|
184
|
+
const headers = [
|
|
185
|
+
{
|
|
186
|
+
title: "Date",
|
|
187
|
+
value: "createdAt",
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
title: "E-mail",
|
|
191
|
+
value: "email",
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
title: "Action",
|
|
195
|
+
value: "action-table",
|
|
196
|
+
},
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
const status = (useRoute().params.status as string) ?? "";
|
|
200
|
+
|
|
201
|
+
const { getVerifications: _getVerifications, cancelUserInvitation } =
|
|
202
|
+
useVerification();
|
|
203
|
+
|
|
204
|
+
const page = ref(1);
|
|
205
|
+
const pages = ref(0);
|
|
206
|
+
const pageRange = ref("-- - -- of --");
|
|
207
|
+
|
|
208
|
+
const message = ref("");
|
|
209
|
+
const messageSnackbar = ref(false);
|
|
210
|
+
const messageColor = ref("");
|
|
211
|
+
|
|
212
|
+
const items = ref<Array<TMiniVerification>>([]);
|
|
213
|
+
const { headerSearch } = useLocal();
|
|
214
|
+
const { formatDate } = useUtils();
|
|
215
|
+
|
|
216
|
+
function showMessage(msg: string, color: string) {
|
|
217
|
+
message.value = msg;
|
|
218
|
+
messageColor.value = color;
|
|
219
|
+
messageSnackbar.value = true;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const {
|
|
223
|
+
data: getVerificationsReq,
|
|
224
|
+
refresh: getVerifications,
|
|
225
|
+
status: getVerificationsStatus,
|
|
226
|
+
} = useLazyAsyncData<{
|
|
227
|
+
items: TMiniVerification[];
|
|
228
|
+
pages: number;
|
|
229
|
+
pageRange: string;
|
|
230
|
+
}>("get-verifications" + props.status, () =>
|
|
231
|
+
_getVerifications({
|
|
232
|
+
page: page.value,
|
|
233
|
+
status: props.status,
|
|
234
|
+
search: headerSearch.value,
|
|
235
|
+
type: "user-invite",
|
|
236
|
+
})
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
const loading = computed(() => getVerificationsStatus.value === "pending");
|
|
240
|
+
|
|
241
|
+
watchEffect(() => {
|
|
242
|
+
if (getVerificationsReq.value) {
|
|
243
|
+
items.value = getVerificationsReq.value.items;
|
|
244
|
+
pages.value = getVerificationsReq.value.pages;
|
|
245
|
+
pageRange.value = getVerificationsReq.value.pageRange;
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
watch([page, headerSearch], () => {
|
|
250
|
+
getVerifications();
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const confirmDialog = ref(false);
|
|
254
|
+
const selectedInviteId = ref<string | null>(null);
|
|
255
|
+
const cancelLoading = ref(false);
|
|
256
|
+
|
|
257
|
+
function openConfirmDialog(id: string) {
|
|
258
|
+
selectedInviteId.value = id;
|
|
259
|
+
confirmDialog.value = true;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async function onConfirmCancel() {
|
|
263
|
+
if (!selectedInviteId.value) return;
|
|
264
|
+
|
|
265
|
+
cancelLoading.value = true;
|
|
266
|
+
try {
|
|
267
|
+
const res = await cancelUserInvitation(selectedInviteId.value);
|
|
268
|
+
|
|
269
|
+
showMessage(res.message || "Invitation cancelled successfully", "success");
|
|
270
|
+
await getVerifications();
|
|
271
|
+
confirmDialog.value = false;
|
|
272
|
+
selectedInviteId.value = null;
|
|
273
|
+
} catch (error: any) {
|
|
274
|
+
showMessage(error?.message || "Failed to cancel invitation", "error");
|
|
275
|
+
}
|
|
276
|
+
cancelLoading.value = false;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
watchEffect(() => {
|
|
280
|
+
if (!props.viewInvitations) {
|
|
281
|
+
useRouter().back();
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
</script>
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<v-app-bar scroll-behavior="elevate" scroll-threshold="200">
|
|
3
|
-
<v-app-bar-nav-icon
|
|
3
|
+
<v-app-bar-nav-icon
|
|
4
|
+
v-if="!props.hideNavIcon"
|
|
5
|
+
@click="drawer = !drawer"
|
|
6
|
+
></v-app-bar-nav-icon>
|
|
4
7
|
|
|
5
8
|
<template #append>
|
|
6
9
|
<v-btn
|
|
@@ -91,6 +94,13 @@
|
|
|
91
94
|
<script setup lang="ts">
|
|
92
95
|
import { useTheme } from "vuetify";
|
|
93
96
|
|
|
97
|
+
const props = defineProps({
|
|
98
|
+
hideNavIcon: {
|
|
99
|
+
type: Boolean,
|
|
100
|
+
default: false,
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
|
|
94
104
|
const menu = defineModel("menu", { type: Boolean });
|
|
95
105
|
|
|
96
106
|
const search = defineModel("search", { type: String });
|
|
@@ -99,7 +109,7 @@ const { drawer } = useLocal();
|
|
|
99
109
|
|
|
100
110
|
const { redirect } = useUtils();
|
|
101
111
|
|
|
102
|
-
const { APP_ACCOUNT, APP_NAME } = useRuntimeConfig().public;
|
|
112
|
+
const { APP_ACCOUNT, APP_MAIN, APP_NAME } = useRuntimeConfig().public;
|
|
103
113
|
|
|
104
114
|
const theme = useTheme();
|
|
105
115
|
|
|
@@ -114,8 +124,8 @@ const profile = computed(() => {
|
|
|
114
124
|
});
|
|
115
125
|
|
|
116
126
|
function logout() {
|
|
117
|
-
if (APP_NAME.toLowerCase() !== "
|
|
118
|
-
redirect(
|
|
127
|
+
if (APP_NAME.toLowerCase() !== "main") {
|
|
128
|
+
redirect(APP_MAIN, "logout");
|
|
119
129
|
return;
|
|
120
130
|
}
|
|
121
131
|
|