@7365admin1/layer-common 3.1.2 → 3.1.3-staging.40
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/.github/workflows/publish-staging.yml +11 -0
- package/components/AddEqupmentForm.vue +52 -1
- package/components/CameraMain.vue +17 -9
- package/components/DashboardMain.vue +231 -87
- package/components/EquipmentManagementMain.vue +56 -0
- package/components/InvitationMain.vue +4 -3
- package/components/ManageChecklistMain.vue +44 -38
- package/components/Nfc/NFCPatrolReportMain.vue +146 -38
- package/components/Nfc/PatrolReport/DailyReportTab.vue +10 -6
- package/components/Nfc/PatrolReport/MonthlyReportTab.vue +19 -9
- package/components/Nfc/PatrolReport/MonthlyReportTable.vue +69 -0
- package/components/Nfc/PatrolReport/PatrolActivityTable.vue +20 -0
- package/components/Nfc/PatrolReport/PatrolReportTab.vue +5 -3
- package/components/Nfc/PatrolReport/ReportDatePicker.vue +61 -0
- package/components/Nfc/PatrolReport/ReportFilters.vue +52 -20
- package/components/Nfc/PatrolReport/SummaryReportTable.vue +22 -0
- package/components/PhotoUpload.vue +19 -4
- package/components/ServiceProviderMain.vue +59 -25
- package/components/VisitorForm.vue +78 -18
- package/components/VisitorManagement.vue +12 -16
- package/composables/useCleaningSchedulePermission.ts +16 -51
- package/composables/useEquipment.ts +2 -0
- package/composables/useLocalAuth.ts +1 -1
- package/composables/useNFCPatrolDailyReport.ts +3 -4
- package/composables/useNFCPatrolMonthlyReport.ts +62 -30
- package/composables/useNFCPatrolReport.ts +2 -0
- package/composables/useServiceProvider.ts +21 -0
- package/package.json +1 -1
- package/types/customer.d.ts +2 -1
- package/types/equipment.d.ts +5 -1
- package/types/nfc-patrol-report.ts +19 -1
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="mt-6">
|
|
3
|
+
<div class="text-h6 font-weight-bold mb-4">
|
|
4
|
+
{{ title }}
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<v-table class="border rounded-lg">
|
|
8
|
+
<thead>
|
|
9
|
+
<tr>
|
|
10
|
+
<th class="text-left font-weight-bold">Month</th>
|
|
11
|
+
<th class="text-center font-weight-bold">Checked</th>
|
|
12
|
+
<th class="text-center font-weight-bold">% Checked</th>
|
|
13
|
+
<th class="text-center font-weight-bold">Missed</th>
|
|
14
|
+
<th class="text-center font-weight-bold">% Missed</th>
|
|
15
|
+
</tr>
|
|
16
|
+
</thead>
|
|
17
|
+
|
|
18
|
+
<tbody>
|
|
19
|
+
<tr
|
|
20
|
+
v-for="row in rows"
|
|
21
|
+
:key="row.month"
|
|
22
|
+
>
|
|
23
|
+
<td>{{ row.month }}</td>
|
|
24
|
+
|
|
25
|
+
<td class="text-center">
|
|
26
|
+
{{ row.checked }}
|
|
27
|
+
</td>
|
|
28
|
+
|
|
29
|
+
<td class="text-center">
|
|
30
|
+
{{ row.checkedPercentage }}%
|
|
31
|
+
</td>
|
|
32
|
+
|
|
33
|
+
<td class="text-center">
|
|
34
|
+
{{ row.missed }}
|
|
35
|
+
</td>
|
|
36
|
+
|
|
37
|
+
<td class="text-center">
|
|
38
|
+
{{ row.missedPercentage }}%
|
|
39
|
+
</td>
|
|
40
|
+
</tr>
|
|
41
|
+
</tbody>
|
|
42
|
+
</v-table>
|
|
43
|
+
</div>
|
|
44
|
+
</template>
|
|
45
|
+
|
|
46
|
+
<script setup lang="ts">
|
|
47
|
+
import type { NFCPatrolMonthlyReportRow } from "../../../types/nfc-patrol-report";
|
|
48
|
+
|
|
49
|
+
defineProps<{
|
|
50
|
+
title: string;
|
|
51
|
+
rows: NFCPatrolMonthlyReportRow[];
|
|
52
|
+
}>();
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<style scoped>
|
|
56
|
+
.v-table {
|
|
57
|
+
border: 1px solid rgb(var(--v-theme-outline));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
thead th {
|
|
61
|
+
background: transparent;
|
|
62
|
+
white-space: nowrap;
|
|
63
|
+
font-weight: 600;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
tbody td {
|
|
67
|
+
height: 56px;
|
|
68
|
+
}
|
|
69
|
+
</style>
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
<thead>
|
|
8
8
|
<tr>
|
|
9
9
|
<th>Checkpoints</th>
|
|
10
|
+
<th>Security Personnel</th>
|
|
11
|
+
<th>Signature</th>
|
|
10
12
|
<th>Start - End Time</th>
|
|
11
13
|
<th>
|
|
12
14
|
Travel time<br />
|
|
@@ -23,6 +25,17 @@
|
|
|
23
25
|
@click="handleActivityClick(activity)"
|
|
24
26
|
>
|
|
25
27
|
<td>{{ activity.checkpoint }}</td>
|
|
28
|
+
<td>{{ activity.guardName || "-" }}</td>
|
|
29
|
+
<td>
|
|
30
|
+
<v-img
|
|
31
|
+
v-if="activity.guardSignatureFileId"
|
|
32
|
+
:src="getFileUrl(activity.guardSignatureFileId)"
|
|
33
|
+
:alt="`${activity.guardName || 'Guard'} signature`"
|
|
34
|
+
class="guard-signature"
|
|
35
|
+
contain
|
|
36
|
+
/>
|
|
37
|
+
<span v-else>-</span>
|
|
38
|
+
</td>
|
|
26
39
|
<td>
|
|
27
40
|
<v-icon size="small" class="mr-1">mdi-clock-outline</v-icon>
|
|
28
41
|
{{ activity.startTime }}
|
|
@@ -59,6 +72,8 @@ defineProps<{
|
|
|
59
72
|
activities: NFCPatrolActivity[];
|
|
60
73
|
}>();
|
|
61
74
|
|
|
75
|
+
const { getFileUrl } = useFile();
|
|
76
|
+
|
|
62
77
|
const emit = defineEmits<{
|
|
63
78
|
remarks: [activity: NFCPatrolActivity];
|
|
64
79
|
}>();
|
|
@@ -78,4 +93,9 @@ function handleActivityClick(activity: NFCPatrolActivity) {
|
|
|
78
93
|
.cursor-pointer {
|
|
79
94
|
cursor: pointer;
|
|
80
95
|
}
|
|
96
|
+
|
|
97
|
+
.guard-signature {
|
|
98
|
+
width: 80px;
|
|
99
|
+
height: 40px;
|
|
100
|
+
}
|
|
81
101
|
</style>
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
:model-value="filters"
|
|
5
5
|
:options="options"
|
|
6
6
|
@update:model-value="$emit('update:filters', $event)"
|
|
7
|
-
@export="$emit('export')"
|
|
7
|
+
@export="(type) => $emit('export', type)"
|
|
8
8
|
/>
|
|
9
|
-
|
|
9
|
+
<div class="report-export">
|
|
10
10
|
<v-divider :thickness="1" class="border-opacity-100" />
|
|
11
11
|
|
|
12
12
|
<template v-if="report">
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
<ReportEmptyState v-else-if="!loading" />
|
|
31
31
|
|
|
32
32
|
<RemarksDialog v-model="dialogRemarks" :remarks="selectedActivityRemarks" />
|
|
33
|
+
</div>
|
|
33
34
|
</div>
|
|
34
35
|
</template>
|
|
35
36
|
|
|
@@ -55,7 +56,7 @@ const props = defineProps<{
|
|
|
55
56
|
|
|
56
57
|
defineEmits<{
|
|
57
58
|
"update:filters": [value: NFCPatrolReportFilters];
|
|
58
|
-
export: [];
|
|
59
|
+
export: [type: "pdf" | "csv"];
|
|
59
60
|
}>();
|
|
60
61
|
|
|
61
62
|
const {
|
|
@@ -77,4 +78,5 @@ watch(
|
|
|
77
78
|
},
|
|
78
79
|
{ immediate: true },
|
|
79
80
|
);
|
|
81
|
+
|
|
80
82
|
</script>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="d-flex flex-column">
|
|
3
|
+
<v-text-field
|
|
4
|
+
v-bind="$attrs"
|
|
5
|
+
:model-value="formattedDate"
|
|
6
|
+
autocomplete="off"
|
|
7
|
+
placeholder="DD/MM/YY"
|
|
8
|
+
readonly
|
|
9
|
+
@click="openDatePicker"
|
|
10
|
+
>
|
|
11
|
+
<template #append-inner>
|
|
12
|
+
<v-icon icon="mdi-calendar" @click.stop="openDatePicker" />
|
|
13
|
+
</template>
|
|
14
|
+
</v-text-field>
|
|
15
|
+
|
|
16
|
+
<input
|
|
17
|
+
ref="dateInput"
|
|
18
|
+
v-model="date"
|
|
19
|
+
class="native-date-input"
|
|
20
|
+
type="date"
|
|
21
|
+
/>
|
|
22
|
+
</div>
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<script setup lang="ts">
|
|
26
|
+
defineOptions({ inheritAttrs: false });
|
|
27
|
+
|
|
28
|
+
const date = defineModel<string>({ default: "" });
|
|
29
|
+
const dateInput = ref<HTMLInputElement | null>(null);
|
|
30
|
+
|
|
31
|
+
const formattedDate = computed(() => {
|
|
32
|
+
const match = date.value.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
|
33
|
+
|
|
34
|
+
if (!match) return "";
|
|
35
|
+
|
|
36
|
+
const [, year, month, day] = match;
|
|
37
|
+
return `${day}/${month}/${year.slice(-2)}`;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
function openDatePicker() {
|
|
41
|
+
const input = dateInput.value;
|
|
42
|
+
|
|
43
|
+
if (!input) return;
|
|
44
|
+
|
|
45
|
+
if (input.showPicker) {
|
|
46
|
+
input.showPicker();
|
|
47
|
+
} else {
|
|
48
|
+
input.click();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
</script>
|
|
52
|
+
|
|
53
|
+
<style scoped>
|
|
54
|
+
.native-date-input {
|
|
55
|
+
position: absolute;
|
|
56
|
+
width: 1px;
|
|
57
|
+
height: 1px;
|
|
58
|
+
opacity: 0;
|
|
59
|
+
pointer-events: none;
|
|
60
|
+
}
|
|
61
|
+
</style>
|
|
@@ -16,7 +16,12 @@
|
|
|
16
16
|
/>
|
|
17
17
|
</v-col>
|
|
18
18
|
|
|
19
|
-
<v-col
|
|
19
|
+
<v-col
|
|
20
|
+
v-if="showTimeFilter"
|
|
21
|
+
cols="12"
|
|
22
|
+
md="2"
|
|
23
|
+
class="px-md-2 mb-2 mb-md-0"
|
|
24
|
+
>
|
|
20
25
|
<v-select
|
|
21
26
|
:model-value="modelValue.timeRange"
|
|
22
27
|
:items="options.timeRanges"
|
|
@@ -30,10 +35,14 @@
|
|
|
30
35
|
/>
|
|
31
36
|
</v-col>
|
|
32
37
|
|
|
33
|
-
<v-col
|
|
34
|
-
|
|
38
|
+
<v-col
|
|
39
|
+
v-if="showDateFilter"
|
|
40
|
+
cols="12"
|
|
41
|
+
md="2"
|
|
42
|
+
class="px-md-2 mb-2 mb-md-0"
|
|
43
|
+
>
|
|
44
|
+
<ReportDatePicker
|
|
35
45
|
:model-value="modelValue.date"
|
|
36
|
-
type="date"
|
|
37
46
|
variant="outlined"
|
|
38
47
|
density="comfortable"
|
|
39
48
|
rounded="lg"
|
|
@@ -46,17 +55,31 @@
|
|
|
46
55
|
<v-spacer />
|
|
47
56
|
|
|
48
57
|
<v-col cols="12" md="2" class="pl-md-2">
|
|
49
|
-
<v-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
<v-menu>
|
|
59
|
+
<template #activator="{ props: menuProps }">
|
|
60
|
+
<v-btn
|
|
61
|
+
v-bind="menuProps"
|
|
62
|
+
variant="outlined"
|
|
63
|
+
block
|
|
64
|
+
size="large"
|
|
65
|
+
rounded="lg"
|
|
66
|
+
class="text-none"
|
|
67
|
+
prepend-icon="mdi-export"
|
|
68
|
+
>
|
|
69
|
+
Export
|
|
70
|
+
</v-btn>
|
|
71
|
+
</template>
|
|
72
|
+
|
|
73
|
+
<v-list density="compact">
|
|
74
|
+
<v-list-item @click="$emit('export', 'pdf')">
|
|
75
|
+
<v-list-item-title>PDF</v-list-item-title>
|
|
76
|
+
</v-list-item>
|
|
77
|
+
|
|
78
|
+
<v-list-item @click="$emit('export', 'csv')">
|
|
79
|
+
<v-list-item-title>CSV</v-list-item-title>
|
|
80
|
+
</v-list-item>
|
|
81
|
+
</v-list>
|
|
82
|
+
</v-menu>
|
|
60
83
|
</v-col>
|
|
61
84
|
</v-row>
|
|
62
85
|
</template>
|
|
@@ -66,15 +89,24 @@ import type {
|
|
|
66
89
|
NFCPatrolReportFilterOptions,
|
|
67
90
|
NFCPatrolReportFilters,
|
|
68
91
|
} from "../../../types/nfc-patrol-report";
|
|
92
|
+
import ReportDatePicker from "./ReportDatePicker.vue";
|
|
69
93
|
|
|
70
|
-
const props =
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
94
|
+
const props = withDefaults(
|
|
95
|
+
defineProps<{
|
|
96
|
+
modelValue: NFCPatrolReportFilters;
|
|
97
|
+
options: NFCPatrolReportFilterOptions;
|
|
98
|
+
showTimeFilter?: boolean;
|
|
99
|
+
showDateFilter?: boolean;
|
|
100
|
+
}>(),
|
|
101
|
+
{
|
|
102
|
+
showTimeFilter: true,
|
|
103
|
+
showDateFilter: true,
|
|
104
|
+
},
|
|
105
|
+
);;
|
|
74
106
|
|
|
75
107
|
const emit = defineEmits<{
|
|
76
108
|
"update:modelValue": [value: NFCPatrolReportFilters];
|
|
77
|
-
export: [];
|
|
109
|
+
export: [type: "pdf" | "csv"];
|
|
78
110
|
}>();
|
|
79
111
|
|
|
80
112
|
function updateFilter(key: keyof NFCPatrolReportFilters, value: unknown) {
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
<tr>
|
|
9
9
|
<th>Patrol Route Name</th>
|
|
10
10
|
<th>Security Check Schedule</th>
|
|
11
|
+
<th>Security Personnel</th>
|
|
12
|
+
<th>Signature</th>
|
|
11
13
|
<th>Checked</th>
|
|
12
14
|
<th>Missed</th>
|
|
13
15
|
<th>Status</th>
|
|
@@ -20,6 +22,17 @@
|
|
|
20
22
|
<v-icon size="small" class="mr-1">mdi-clock-outline</v-icon>
|
|
21
23
|
{{ row.securityCheckSchedule }}
|
|
22
24
|
</td>
|
|
25
|
+
<td>{{ row.guardName || "-" }}</td>
|
|
26
|
+
<td>
|
|
27
|
+
<v-img
|
|
28
|
+
v-if="row.guardSignatureFileId"
|
|
29
|
+
:src="getFileUrl(row.guardSignatureFileId)"
|
|
30
|
+
:alt="`${row.guardName || 'Guard'} signature`"
|
|
31
|
+
class="guard-signature"
|
|
32
|
+
contain
|
|
33
|
+
/>
|
|
34
|
+
<span v-else>-</span>
|
|
35
|
+
</td>
|
|
23
36
|
<td>{{ row.checked }}</td>
|
|
24
37
|
<td>{{ row.missed }}</td>
|
|
25
38
|
<td>
|
|
@@ -45,7 +58,16 @@ defineProps<{
|
|
|
45
58
|
rows: NFCPatrolReportSummaryRow[];
|
|
46
59
|
}>();
|
|
47
60
|
|
|
61
|
+
const { getFileUrl } = useFile();
|
|
62
|
+
|
|
48
63
|
function statusClass(status: NFCPatrolReportSummaryStatus) {
|
|
49
64
|
return status === "Completed" ? "text-success" : "text-error";
|
|
50
65
|
}
|
|
51
66
|
</script>
|
|
67
|
+
|
|
68
|
+
<style scoped>
|
|
69
|
+
.guard-signature {
|
|
70
|
+
width: 80px;
|
|
71
|
+
height: 40px;
|
|
72
|
+
}
|
|
73
|
+
</style>
|
|
@@ -59,6 +59,21 @@
|
|
|
59
59
|
</v-col>
|
|
60
60
|
</v-row>
|
|
61
61
|
|
|
62
|
+
<v-dialog v-model="showPhotoPreview" max-width="900">
|
|
63
|
+
<v-card>
|
|
64
|
+
<v-card-actions class="pa-2 justify-end">
|
|
65
|
+
<v-btn
|
|
66
|
+
icon="mdi-close"
|
|
67
|
+
variant="text"
|
|
68
|
+
@click="showPhotoPreview = false"
|
|
69
|
+
/>
|
|
70
|
+
</v-card-actions>
|
|
71
|
+
<v-card-text class="pa-2 pt-0">
|
|
72
|
+
<v-img :src="selectedPhoto" contain max-height="80vh" />
|
|
73
|
+
</v-card-text>
|
|
74
|
+
</v-card>
|
|
75
|
+
</v-dialog>
|
|
76
|
+
|
|
62
77
|
<v-dialog v-model="photoOptionsDialog" max-width="420">
|
|
63
78
|
<v-card>
|
|
64
79
|
<v-card-title class="d-flex align-center pa-4">
|
|
@@ -196,6 +211,8 @@ const photos = ref<string[]>([...props.modelValue]);
|
|
|
196
211
|
const photosIds = ref<string[]>([...props.photoIds]);
|
|
197
212
|
const photoOptionsDialog = ref(false);
|
|
198
213
|
const showCameraDialog = ref(false);
|
|
214
|
+
const showPhotoPreview = ref(false);
|
|
215
|
+
const selectedPhoto = ref("");
|
|
199
216
|
const videoRef = ref<HTMLVideoElement | null>(null);
|
|
200
217
|
const canvasRef = ref<HTMLCanvasElement | null>(null);
|
|
201
218
|
const fileInputRef = ref<HTMLInputElement | null>(null);
|
|
@@ -205,10 +222,8 @@ const uploading = ref(false);
|
|
|
205
222
|
const { addFile } = useFile();
|
|
206
223
|
|
|
207
224
|
function viewPhoto(photoData: string) {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
link.target = "_blank";
|
|
211
|
-
link.click();
|
|
225
|
+
selectedPhoto.value = photoData;
|
|
226
|
+
showPhotoPreview.value = true;
|
|
212
227
|
}
|
|
213
228
|
|
|
214
229
|
watch(
|
|
@@ -548,7 +548,7 @@
|
|
|
548
548
|
variant="flat"
|
|
549
549
|
class="text-none sp-invite-submit-btn"
|
|
550
550
|
height="76"
|
|
551
|
-
:disabled="!
|
|
551
|
+
:disabled="!canSubmitInvite"
|
|
552
552
|
:loading="disableServiceProvider"
|
|
553
553
|
@click="submitServiceProviderInvite()"
|
|
554
554
|
>
|
|
@@ -574,6 +574,7 @@ type ListTab = "active" | "pending" | "inactive";
|
|
|
574
574
|
|
|
575
575
|
type TableRow = {
|
|
576
576
|
_id: string;
|
|
577
|
+
userId: string;
|
|
577
578
|
serviceProviderOrgId: string;
|
|
578
579
|
companyName: string;
|
|
579
580
|
email: string;
|
|
@@ -733,11 +734,10 @@ const {
|
|
|
733
734
|
updateStatus: updateServiceProviderStatus,
|
|
734
735
|
invite,
|
|
735
736
|
} = useServiceProvider();
|
|
736
|
-
const { getAll: getAllMembers } = useMember();
|
|
737
|
+
const { getAll: getAllMembers, getAllByUserId } = useMember();
|
|
737
738
|
|
|
738
739
|
const { getVerifications, cancelUserInvitation, getVerificationByEmail } =
|
|
739
740
|
useVerification();
|
|
740
|
-
const { getOrgsByMembership } = useMember();
|
|
741
741
|
|
|
742
742
|
const existingAccount = ref<any>(null);
|
|
743
743
|
const checkingExistingAccount = ref(false);
|
|
@@ -759,41 +759,77 @@ const createInviteDialog = ref(false);
|
|
|
759
759
|
|
|
760
760
|
const { getByUserType } = useMember();
|
|
761
761
|
const { getOrgs } = useOrg();
|
|
762
|
+
const { getByEmail } = useServiceProvider();
|
|
762
763
|
|
|
763
764
|
async function checkExistingAccount(email: string) {
|
|
764
765
|
existingAccount.value = null;
|
|
766
|
+
messageServiceProvider.value = "";
|
|
765
767
|
|
|
766
768
|
if (!email.trim() || emailRule(email) !== true) return;
|
|
767
769
|
|
|
768
770
|
checkingExistingAccount.value = true;
|
|
769
771
|
|
|
770
772
|
try {
|
|
773
|
+
// Check if user exists
|
|
771
774
|
const user = await getUserByEmail(email);
|
|
772
775
|
|
|
773
|
-
|
|
776
|
+
if (!user?._id) return;
|
|
774
777
|
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
+
// Check if this service provider already exists in current org/site
|
|
779
|
+
const serviceProviders = await getByEmail({
|
|
780
|
+
email,
|
|
781
|
+
orgId: props.orgId,
|
|
782
|
+
siteId: props.siteId,
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
console.log("serviceProviders", serviceProviders);
|
|
786
|
+
|
|
787
|
+
if (serviceProviders?.length) {
|
|
788
|
+
messageServiceProvider.value =
|
|
789
|
+
"This service provider already exists.";
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// Check existing memberships
|
|
794
|
+
const members = await getAllByUserId(user._id);
|
|
778
795
|
|
|
779
|
-
const
|
|
780
|
-
(
|
|
796
|
+
const appMember = (members.items ?? []).find(
|
|
797
|
+
(member: any) => member.type === serviceProviderInvite.value.app
|
|
781
798
|
);
|
|
782
799
|
|
|
783
|
-
if (
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
800
|
+
if (!appMember) return;
|
|
801
|
+
|
|
802
|
+
// Already belongs to the same org/site
|
|
803
|
+
if (
|
|
804
|
+
appMember.org === props.orgId &&
|
|
805
|
+
(appMember.siteId || "") === (props.siteId || "")
|
|
806
|
+
) {
|
|
807
|
+
messageServiceProvider.value =
|
|
808
|
+
"This user is already a member of this service provider.";
|
|
809
|
+
return;
|
|
789
810
|
}
|
|
811
|
+
|
|
812
|
+
// Existing account in another org/site
|
|
813
|
+
existingAccount.value = {
|
|
814
|
+
_id: appMember.org,
|
|
815
|
+
text: appMember.orgName,
|
|
816
|
+
org: appMember.org,
|
|
817
|
+
orgName: appMember.orgName,
|
|
818
|
+
siteId: appMember.siteId,
|
|
819
|
+
siteName: appMember.siteName,
|
|
820
|
+
};
|
|
790
821
|
} catch (error) {
|
|
791
822
|
existingAccount.value = null;
|
|
792
823
|
} finally {
|
|
793
824
|
checkingExistingAccount.value = false;
|
|
794
825
|
}
|
|
795
826
|
}
|
|
796
|
-
|
|
827
|
+
const canSubmitInvite = computed(() => {
|
|
828
|
+
return (
|
|
829
|
+
validServiceProvider.value &&
|
|
830
|
+
!messageServiceProvider.value
|
|
831
|
+
);
|
|
832
|
+
});
|
|
797
833
|
watch(
|
|
798
834
|
() => serviceProviderInvite.value.email,
|
|
799
835
|
async (value) => {
|
|
@@ -939,6 +975,7 @@ function formatEmailCell(value: unknown): string {
|
|
|
939
975
|
function mapProviderRows(raw: Record<string, any>[]): TableRow[] {
|
|
940
976
|
return raw.map((row) => ({
|
|
941
977
|
_id: String(row._id ?? ""),
|
|
978
|
+
userId: String(row.user ?? ""),
|
|
942
979
|
serviceProviderOrgId: String(row.serviceProviderOrgId ?? ""),
|
|
943
980
|
companyName: row.name ?? "-",
|
|
944
981
|
email: formatEmailCell(row.email),
|
|
@@ -961,6 +998,7 @@ function inviteTypeLabel(t: string) {
|
|
|
961
998
|
function mapInviteRows(raw: Record<string, any>[]): TableRow[] {
|
|
962
999
|
return raw.map((row) => ({
|
|
963
1000
|
_id: String(row._id ?? ""),
|
|
1001
|
+
userId: String(row.user ?? ""),
|
|
964
1002
|
serviceProviderOrgId: String(row.metadata?.serviceProviderOrgId ?? ""),
|
|
965
1003
|
companyName: row.metadata?.name || "-",
|
|
966
1004
|
email: formatEmailCell(row.email),
|
|
@@ -1029,16 +1067,12 @@ async function openView(row: TableRow) {
|
|
|
1029
1067
|
|
|
1030
1068
|
loadingMembers.value = true;
|
|
1031
1069
|
try {
|
|
1032
|
-
const data = await
|
|
1033
|
-
|
|
1034
|
-
limit: 1000,
|
|
1035
|
-
org: row.serviceProviderOrgId,
|
|
1036
|
-
status: "active",
|
|
1037
|
-
});
|
|
1070
|
+
const data = await getAllByUserId(row.userId);
|
|
1071
|
+
|
|
1038
1072
|
serviceProviderMembers.value = (data.items ?? []).map((member: any) => ({
|
|
1039
|
-
_id:
|
|
1040
|
-
name: member.name ||
|
|
1041
|
-
role: member.
|
|
1073
|
+
_id: member._id,
|
|
1074
|
+
name: member.name || "-",
|
|
1075
|
+
role: member.role || "-",
|
|
1042
1076
|
}));
|
|
1043
1077
|
} catch (error: any) {
|
|
1044
1078
|
showMessage(errorConverter(error), "error");
|