@eeplatform/nuxt-layer-common 1.2.2 → 1.2.4

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # @eeplatform/nuxt-layer-common
2
2
 
3
+ ## 1.2.4
4
+
5
+ ### Patch Changes
6
+
7
+ - 9036391: Revise API plugin
8
+
9
+ ## 1.2.3
10
+
11
+ ### Patch Changes
12
+
13
+ - 5a927d9: Revise school and invite components
14
+
3
15
  ## 1.2.2
4
16
 
5
17
  ### Patch Changes
@@ -0,0 +1,206 @@
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"> Invite </span>
6
+ </v-row>
7
+ </v-toolbar>
8
+ <v-card-text style="max-height: 100vh; overflow-y: auto" class="pb-0">
9
+ <v-form v-model="validInvite">
10
+ <v-row no-gutters>
11
+ <v-col cols="12">
12
+ <v-row no-gutters>
13
+ <InputLabel class="text-capitalize" title="E-mail" required />
14
+ <v-col cols="12">
15
+ <v-text-field
16
+ v-model="invite.email"
17
+ density="comfortable"
18
+ :rules="[requiredRule, emailRule]"
19
+ ></v-text-field>
20
+ </v-col>
21
+ </v-row>
22
+ </v-col>
23
+
24
+ <v-col cols="12" class="my-2">
25
+ <v-row no-gutters>
26
+ <v-col cols="12">
27
+ <v-input
28
+ :error-messages="invite.role ? [] : ['Role is required']"
29
+ >
30
+ <v-menu z-index="10000">
31
+ <template #activator="{ props }">
32
+ <v-btn
33
+ block
34
+ variant="flat"
35
+ color="black"
36
+ class="text-none"
37
+ height="48"
38
+ v-bind="props"
39
+ >
40
+ {{ invite.role ? roleName : "Select Role" }}
41
+ </v-btn>
42
+ </template>
43
+
44
+ <v-list class="pa-0">
45
+ <v-list-item v-if="!roles.length"> No data </v-list-item>
46
+
47
+ <v-list-item
48
+ v-for="role in roles"
49
+ :key="role._id"
50
+ @click="invite.role = role._id"
51
+ >
52
+ <template #prepend>
53
+ <v-icon v-if="invite.role === role._id">
54
+ mdi-check
55
+ </v-icon>
56
+ </template>
57
+
58
+ <v-list-item-title
59
+ class="text-subtitle-2 font-weight-medium"
60
+ >
61
+ {{ role.name }}
62
+ </v-list-item-title>
63
+ </v-list-item>
64
+ </v-list>
65
+ </v-menu>
66
+ </v-input>
67
+ </v-col>
68
+ </v-row>
69
+ </v-col>
70
+
71
+ <v-col v-if="messageInvite" cols="12" class="text-center mb-4">
72
+ <span
73
+ class="text-none text-subtitle-2 font-weight-medium text-error"
74
+ >
75
+ {{ messageInvite }}
76
+ </span>
77
+ </v-col>
78
+ </v-row>
79
+ </v-form>
80
+ </v-card-text>
81
+
82
+ <v-toolbar class="pa-0" density="compact">
83
+ <v-row no-gutters>
84
+ <v-col cols="6" class="pa-0">
85
+ <v-btn
86
+ block
87
+ variant="text"
88
+ class="text-none"
89
+ size="large"
90
+ @click="cancel()"
91
+ height="48"
92
+ tile
93
+ >
94
+ Cancel
95
+ </v-btn>
96
+ </v-col>
97
+
98
+ <v-col cols="6">
99
+ <v-btn
100
+ block
101
+ variant="flat"
102
+ color="black"
103
+ class="text-none"
104
+ size="large"
105
+ @click="submitInvite()"
106
+ height="48"
107
+ tile
108
+ :disabled="!validInvite"
109
+ >
110
+ Submit
111
+ </v-btn>
112
+ </v-col>
113
+ </v-row>
114
+ </v-toolbar>
115
+ </v-card>
116
+ </template>
117
+
118
+ <script setup lang="ts">
119
+ const prop = defineProps({
120
+ app: {
121
+ type: String,
122
+ required: true,
123
+ default: "admin",
124
+ },
125
+ org: {
126
+ type: String,
127
+ default: "",
128
+ },
129
+ orgName: {
130
+ type: String,
131
+ default: "",
132
+ },
133
+ });
134
+
135
+ const { requiredRule, emailRule } = useUtils();
136
+
137
+ const invite = ref({
138
+ email: "",
139
+ app: "",
140
+ role: "",
141
+ });
142
+
143
+ const searchRole = ref("");
144
+ const roles = ref<Array<Record<string, any>>>([]);
145
+
146
+ const { getRoles } = useRole();
147
+
148
+ const { data: getRolesData, status: getRolesStatus } = await useLazyAsyncData(
149
+ "get-roles",
150
+ () =>
151
+ getRoles({
152
+ search: searchRole.value,
153
+ type: prop.app,
154
+ id: prop.org,
155
+ limit: 20,
156
+ })
157
+ );
158
+
159
+ const loading = computed(() => {
160
+ return getRolesStatus.value === "pending";
161
+ });
162
+
163
+ watchEffect(() => {
164
+ if (getRolesData.value) {
165
+ roles.value = getRolesData.value.items;
166
+ }
167
+ });
168
+
169
+ const validInvite = ref(false);
170
+
171
+ const { inviteUser } = useUser();
172
+
173
+ const messageInvite = ref("");
174
+
175
+ const roleName = computed(() => {
176
+ return roles.value.find((i) => i._id === invite.value.role)?.name || "";
177
+ });
178
+
179
+ const emit = defineEmits(["success", "cancel"]);
180
+
181
+ async function submitInvite() {
182
+ messageInvite.value = "";
183
+ try {
184
+ await inviteUser({
185
+ email: invite.value.email,
186
+ app: prop.app,
187
+ role: invite.value.role,
188
+ roleName: roleName.value,
189
+ org: prop.org,
190
+ orgName: prop.orgName,
191
+ });
192
+
193
+ await emit("success");
194
+ } catch (error: any) {
195
+ messageInvite.value =
196
+ error.response?._data?.message || "Failed to invite user";
197
+ }
198
+ }
199
+
200
+ function cancel() {
201
+ invite.value.email = "";
202
+ invite.value.app = prop.app;
203
+ invite.value.role = "";
204
+ emit("cancel");
205
+ }
206
+ </script>
@@ -6,7 +6,7 @@
6
6
  class="text-none mr-2"
7
7
  rounded="pill"
8
8
  variant="tonal"
9
- :to="props.inviteRoute"
9
+ @click="dialogInvite = true"
10
10
  size="large"
11
11
  v-if="props.inviteMember"
12
12
  >
@@ -46,20 +46,14 @@
46
46
  <template #extension>
47
47
  <v-tabs>
48
48
  <v-tab
49
+ v-for="status in statusFilter"
50
+ :key="status.text"
49
51
  :to="{
50
- name: 'org-organization-invitations-status-status',
51
- params: { status: 'pending', organization },
52
+ name: props.baseRoute,
53
+ params: status.params,
52
54
  }"
53
55
  >
54
- Pending
55
- </v-tab>
56
- <v-tab
57
- :to="{
58
- name: 'org-organization-invitations-status-status',
59
- params: { status: 'expired', organization },
60
- }"
61
- >
62
- Expired
56
+ {{ status.text }}
63
57
  </v-tab>
64
58
  </v-tabs>
65
59
  </template>
@@ -86,6 +80,15 @@
86
80
  </v-data-table>
87
81
  </v-card>
88
82
  </v-col>
83
+
84
+ <v-dialog v-model="dialogInvite" width="400" persistent>
85
+ <InvitationForm
86
+ :app="props.app"
87
+ :org="props.org"
88
+ @cancel="dialogInvite = false"
89
+ @success="inviteSuccess()"
90
+ />
91
+ </v-dialog>
89
92
  </v-row>
90
93
  </template>
91
94
 
@@ -95,6 +98,10 @@ const props = defineProps({
95
98
  type: String,
96
99
  default: "active",
97
100
  },
101
+ org: {
102
+ type: String,
103
+ default: "",
104
+ },
98
105
  app: {
99
106
  type: String,
100
107
  default: "organization",
@@ -103,6 +110,10 @@ const props = defineProps({
103
110
  type: Boolean,
104
111
  default: false,
105
112
  },
113
+ baseRoute: {
114
+ type: String,
115
+ default: "invitations-status-status",
116
+ },
106
117
  inviteRoute: {
107
118
  type: Object as PropType<Record<string, any>>,
108
119
  default: () => ({
@@ -112,8 +123,6 @@ const props = defineProps({
112
123
  },
113
124
  });
114
125
 
115
- const organization = (useRoute().params.organization as string) ?? "";
116
-
117
126
  const headers = [
118
127
  {
119
128
  title: "Date",
@@ -129,6 +138,22 @@ const headers = [
129
138
  },
130
139
  ];
131
140
 
141
+ const statusFilter = computed(() => {
142
+ const items = [
143
+ { text: "Pending", params: { status: "pending" } },
144
+ { text: "Expired", params: { status: "expired" } },
145
+ ];
146
+
147
+ if (props.org) {
148
+ items.map((i) => ({
149
+ ...i,
150
+ params: { ...i.params, organization: props.org },
151
+ }));
152
+ }
153
+
154
+ return items;
155
+ });
156
+
132
157
  const { getVerifications } = useVerification();
133
158
 
134
159
  const page = ref(1);
@@ -186,6 +211,13 @@ watch(selectAll, (curr) => {
186
211
  selected.value.push(...ids);
187
212
  }
188
213
  });
214
+
215
+ const dialogInvite = ref(false);
216
+
217
+ function inviteSuccess() {
218
+ dialogInvite.value = false;
219
+ getInvitations();
220
+ }
189
221
  </script>
190
222
 
191
223
  <style scoped>
@@ -293,11 +293,6 @@ const props = defineProps({
293
293
 
294
294
  value: "roleName",
295
295
  },
296
- {
297
- title: "Organization",
298
-
299
- value: "orgName",
300
- },
301
296
  {
302
297
  title: "Action",
303
298
  value: "action-table",
@@ -379,7 +374,7 @@ function setMember({
379
374
  const roles = ref<Array<Record<string, any>>>([]);
380
375
  const { getRoles } = useRole();
381
376
  const { data: getAllRoleReq } = useLazyAsyncData("get-roles", () =>
382
- getRoles({ org: props.orgId, type: props.type, limit: 20 })
377
+ getRoles({ id: props.orgId, type: props.type, limit: 20 })
383
378
  );
384
379
 
385
380
  watchEffect(() => {
@@ -168,7 +168,6 @@ const props = defineProps({
168
168
  title: "permissions",
169
169
  value: "permissions",
170
170
  },
171
- { title: "App", value: "type" },
172
171
  { title: "Action", value: "action-table" },
173
172
  ],
174
173
  },
@@ -0,0 +1,216 @@
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">
6
+ Create School
7
+ </span>
8
+ </v-row>
9
+ </v-toolbar>
10
+ <v-card-text style="max-height: 100vh; overflow-y: auto">
11
+ <v-form v-model="validForm" :disabled="disable">
12
+ <v-row no-gutters>
13
+ <v-col cols="12" class="mt-2">
14
+ <v-row no-gutters>
15
+ <InputLabel class="text-capitalize" title="School Name" required />
16
+ <v-col cols="12">
17
+ <v-text-field
18
+ v-model="name"
19
+ density="comfortable"
20
+ :rules="[requiredRule]"
21
+ placeholder="Enter school name"
22
+ ></v-text-field>
23
+ </v-col>
24
+ </v-row>
25
+ </v-col>
26
+
27
+ <v-col cols="12" class="mt-2">
28
+ <v-row no-gutters>
29
+ <InputLabel class="text-capitalize" title="Division" required />
30
+ <v-col cols="12">
31
+ <v-select
32
+ v-model="selectedDivision"
33
+ :items="divisionOptions"
34
+ item-title="name"
35
+ item-value="_id"
36
+ density="comfortable"
37
+ :rules="[requiredRule]"
38
+ placeholder="Select a division"
39
+ :loading="divisionsLoading"
40
+ return-object
41
+ ></v-select>
42
+ </v-col>
43
+ </v-row>
44
+ </v-col>
45
+
46
+ <v-col cols="12" class="mt-2">
47
+ <v-row no-gutters>
48
+ <InputLabel class="text-capitalize" title="Principal Name" />
49
+ <v-col cols="12">
50
+ <v-text-field
51
+ v-model="principalName"
52
+ density="comfortable"
53
+ placeholder="Enter principal name (optional)"
54
+ ></v-text-field>
55
+ </v-col>
56
+ </v-row>
57
+ </v-col>
58
+
59
+ <v-col cols="12" class="mt-2">
60
+ <v-row no-gutters>
61
+ <InputLabel class="text-capitalize" title="Address" />
62
+ <v-col cols="12">
63
+ <v-textarea
64
+ v-model="address"
65
+ density="comfortable"
66
+ placeholder="Enter school address (optional)"
67
+ rows="3"
68
+ ></v-textarea>
69
+ </v-col>
70
+ </v-row>
71
+ </v-col>
72
+
73
+ <v-col cols="12" class="mt-2">
74
+ <v-checkbox v-model="createMore" density="comfortable" hide-details>
75
+ <template #label>
76
+ <span class="text-subtitle-2 font-weight-bold">
77
+ Create more
78
+ </span>
79
+ </template>
80
+ </v-checkbox>
81
+ </v-col>
82
+
83
+ <v-col cols="12" class="my-2">
84
+ <v-row no-gutters>
85
+ <v-col cols="12" class="text-center">
86
+ <span
87
+ class="text-none text-subtitle-2 font-weight-medium text-error"
88
+ >
89
+ {{ message }}
90
+ </span>
91
+ </v-col>
92
+ </v-row>
93
+ </v-col>
94
+ </v-row>
95
+ </v-form>
96
+ </v-card-text>
97
+
98
+ <v-toolbar>
99
+ <v-row class="px-6">
100
+ <v-col cols="6">
101
+ <v-btn
102
+ block
103
+ variant="text"
104
+ class="text-none"
105
+ size="large"
106
+ @click="cancel"
107
+ :disabled="disable"
108
+ >
109
+ Cancel
110
+ </v-btn>
111
+ </v-col>
112
+
113
+ <v-col cols="6">
114
+ <v-btn
115
+ block
116
+ variant="flat"
117
+ color="black"
118
+ class="text-none"
119
+ size="large"
120
+ :disabled="!validForm || disable"
121
+ @click="submit"
122
+ :loading="disable"
123
+ >
124
+ Create School
125
+ </v-btn>
126
+ </v-col>
127
+ </v-row>
128
+ </v-toolbar>
129
+ </v-card>
130
+ </template>
131
+
132
+ <script setup lang="ts">
133
+ const emit = defineEmits(["cancel", "success", "success:create-more"]);
134
+
135
+ const validForm = ref(false);
136
+
137
+ const name = ref("");
138
+ const selectedDivision = ref<Record<string, any> | null>(null);
139
+ const principalName = ref("");
140
+ const address = ref("");
141
+ const createMore = ref(false);
142
+ const disable = ref(false);
143
+
144
+ const { requiredRule } = useUtils();
145
+
146
+ const message = ref("");
147
+
148
+ const { createSchool } = useSchool();
149
+ const { getAll: getDivisions } = useDivision();
150
+
151
+ // Load divisions for selection
152
+ const divisionOptions = ref<Array<Record<string, any>>>([]);
153
+ const divisionsLoading = ref(false);
154
+
155
+ onMounted(async () => {
156
+ divisionsLoading.value = true;
157
+ try {
158
+ const response = await getDivisions({ page: 1, limit: 100 });
159
+ divisionOptions.value = response.items || [];
160
+ } catch (error) {
161
+ console.error('Failed to load divisions:', error);
162
+ } finally {
163
+ divisionsLoading.value = false;
164
+ }
165
+ });
166
+
167
+ async function submit() {
168
+ disable.value = true;
169
+ try {
170
+ const payload: Record<string, any> = {
171
+ name: name.value,
172
+ };
173
+
174
+ if (selectedDivision.value) {
175
+ payload.division = selectedDivision.value._id;
176
+ payload.divisionName = selectedDivision.value.name;
177
+ }
178
+
179
+ if (principalName.value.trim()) {
180
+ payload.principalName = principalName.value.trim();
181
+ }
182
+
183
+ if (address.value.trim()) {
184
+ payload.address = address.value.trim();
185
+ }
186
+
187
+ await createSchool(payload);
188
+
189
+ if (createMore.value) {
190
+ name.value = "";
191
+ selectedDivision.value = null;
192
+ principalName.value = "";
193
+ address.value = "";
194
+ message.value = "";
195
+ emit("success:create-more");
196
+ return;
197
+ }
198
+
199
+ emit("success");
200
+ } catch (error: any) {
201
+ message.value = error.response?._data?.message || "Failed to create school";
202
+ } finally {
203
+ disable.value = false;
204
+ }
205
+ }
206
+
207
+ function cancel() {
208
+ name.value = "";
209
+ selectedDivision.value = null;
210
+ principalName.value = "";
211
+ address.value = "";
212
+ createMore.value = false;
213
+ message.value = "";
214
+ emit("cancel");
215
+ }
216
+ </script>