@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.
@@ -0,0 +1,177 @@
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"> Edit School </span>
6
+ </v-row>
7
+ </v-toolbar>
8
+ <v-card-text style="max-height: 100vh; overflow-y: auto">
9
+ <v-form v-model="validForm" :disabled="disable">
10
+ <v-row no-gutters>
11
+ <v-col cols="12" class="mt-2">
12
+ <v-row no-gutters>
13
+ <InputLabel class="text-capitalize" title="School Name" required />
14
+ <v-col cols="12">
15
+ <v-text-field
16
+ v-model="name"
17
+ density="comfortable"
18
+ :rules="[requiredRule]"
19
+ placeholder="Enter school name"
20
+ ></v-text-field>
21
+ </v-col>
22
+ </v-row>
23
+ </v-col>
24
+
25
+ <v-col cols="12" class="mt-2">
26
+ <v-row no-gutters>
27
+ <InputLabel class="text-capitalize" title="Principal Name" />
28
+ <v-col cols="12">
29
+ <v-text-field
30
+ v-model="principalName"
31
+ density="comfortable"
32
+ placeholder="Enter principal name (optional)"
33
+ ></v-text-field>
34
+ </v-col>
35
+ </v-row>
36
+ </v-col>
37
+
38
+ <v-col cols="12" class="mt-2">
39
+ <v-row no-gutters>
40
+ <InputLabel class="text-capitalize" title="Address" />
41
+ <v-col cols="12">
42
+ <v-textarea
43
+ v-model="address"
44
+ density="comfortable"
45
+ placeholder="Enter school address (optional)"
46
+ rows="3"
47
+ ></v-textarea>
48
+ </v-col>
49
+ </v-row>
50
+ </v-col>
51
+
52
+ <v-col cols="12" class="my-2">
53
+ <v-row no-gutters>
54
+ <v-col cols="12" class="text-center">
55
+ <span
56
+ class="text-none text-subtitle-2 font-weight-medium text-error"
57
+ >
58
+ {{ message }}
59
+ </span>
60
+ </v-col>
61
+ </v-row>
62
+ </v-col>
63
+ </v-row>
64
+ </v-form>
65
+ </v-card-text>
66
+
67
+ <v-toolbar>
68
+ <v-row class="px-6">
69
+ <v-col cols="6">
70
+ <v-btn
71
+ block
72
+ variant="text"
73
+ class="text-none"
74
+ size="large"
75
+ @click="cancel"
76
+ :disabled="disable"
77
+ >
78
+ Cancel
79
+ </v-btn>
80
+ </v-col>
81
+
82
+ <v-col cols="6">
83
+ <v-btn
84
+ block
85
+ variant="flat"
86
+ color="black"
87
+ class="text-none"
88
+ size="large"
89
+ :disabled="!validForm || disable || !hasChanges"
90
+ @click="submit"
91
+ :loading="disable"
92
+ >
93
+ Update School
94
+ </v-btn>
95
+ </v-col>
96
+ </v-row>
97
+ </v-toolbar>
98
+ </v-card>
99
+ </template>
100
+
101
+ <script setup lang="ts">
102
+ const props = defineProps({
103
+ school: {
104
+ type: Object as PropType<Record<string, any>>,
105
+ required: true,
106
+ },
107
+ });
108
+
109
+ const emit = defineEmits(["cancel", "success"]);
110
+
111
+ const validForm = ref(false);
112
+
113
+ const name = ref("");
114
+ const principalName = ref("");
115
+ const address = ref("");
116
+ const disable = ref(false);
117
+
118
+ const { requiredRule } = useUtils();
119
+
120
+ const message = ref("");
121
+
122
+ const { updateSchoolField } = useSchool();
123
+
124
+ // Initialize form with existing data
125
+ watchEffect(() => {
126
+ if (props.school) {
127
+ name.value = props.school.name || "";
128
+ principalName.value = props.school.principalName || "";
129
+ address.value = props.school.address || "";
130
+ }
131
+ });
132
+
133
+ const hasChanges = computed(() => {
134
+ if (!props.school) return false;
135
+ return (
136
+ name.value !== (props.school.name || "") ||
137
+ principalName.value !== (props.school.principalName || "") ||
138
+ address.value !== (props.school.address || "")
139
+ );
140
+ });
141
+
142
+ async function submit() {
143
+ if (!props.school?._id) return;
144
+
145
+ disable.value = true;
146
+ try {
147
+ // Update fields that have changed
148
+ if (name.value !== (props.school.name || "")) {
149
+ await updateSchoolField(props.school._id, "name", name.value);
150
+ }
151
+
152
+ if (principalName.value !== (props.school.principalName || "")) {
153
+ await updateSchoolField(props.school._id, "principalName", principalName.value);
154
+ }
155
+
156
+ if (address.value !== (props.school.address || "")) {
157
+ await updateSchoolField(props.school._id, "address", address.value);
158
+ }
159
+
160
+ emit("success");
161
+ } catch (error: any) {
162
+ message.value = error.response?._data?.message || "Failed to update school";
163
+ } finally {
164
+ disable.value = false;
165
+ }
166
+ }
167
+
168
+ function cancel() {
169
+ // Reset to original values
170
+ name.value = props.school?.name || "";
171
+ principalName.value = props.school?.principalName || "";
172
+ address.value = props.school?.address || "";
173
+ message.value = "";
174
+ emit("cancel");
175
+ }
176
+ </script>
177
+
@@ -0,0 +1,429 @@
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="loading"
10
+ >
11
+ <v-toolbar density="compact" color="grey-lighten-4">
12
+ <template #prepend>
13
+ <v-btn fab icon density="comfortable" @click="getSchools()">
14
+ <v-icon>mdi-refresh</v-icon>
15
+ </v-btn>
16
+ </template>
17
+
18
+ <template #append>
19
+ <v-row no-gutters justify="end" align="center">
20
+ <span class="mr-2 text-caption text-fontgray">
21
+ {{ pageRange }}
22
+ </span>
23
+ <local-pagination
24
+ v-model="page"
25
+ :length="pages"
26
+ @update:value="getSchools()"
27
+ />
28
+ </v-row>
29
+ </template>
30
+
31
+ <template #extension>
32
+ <v-tabs v-model="theStatus">
33
+ <v-tab
34
+ v-for="status in statusFilter"
35
+ :key="status.text"
36
+ :to="{
37
+ name: prop.baseRoute,
38
+ params: status.params,
39
+ }"
40
+ class="text-capitalize"
41
+ >
42
+ {{ status.text }}
43
+ </v-tab>
44
+ </v-tabs>
45
+ </template>
46
+ </v-toolbar>
47
+
48
+ <v-data-table
49
+ :headers="headers"
50
+ :items="items"
51
+ item-value="_id"
52
+ items-per-page="20"
53
+ fixed-header
54
+ hide-default-footer
55
+ hide-default-header
56
+ @click:row="tableRowClickHandler"
57
+ style="max-height: calc(100vh - (180px))"
58
+ >
59
+ <template #item.createdAt="{ value }">
60
+ {{ new Date(value).toLocaleDateString() }}
61
+ </template>
62
+ </v-data-table>
63
+ </v-card>
64
+ </v-col>
65
+
66
+ <!-- Create Dialog -->
67
+ <v-dialog v-model="createDialog" width="500" persistent>
68
+ <SchoolFormCreate
69
+ @cancel="createDialog = false"
70
+ @success="successCreate()"
71
+ @success:create-more="getSchools()"
72
+ />
73
+ </v-dialog>
74
+
75
+ <!-- Edit Dialog -->
76
+ <v-dialog v-model="editDialog" width="500" persistent>
77
+ <SchoolFormEdit
78
+ v-if="selectedSchool"
79
+ @cancel="editDialog = false"
80
+ @success="successUpdate()"
81
+ :school="selectedSchool"
82
+ />
83
+ </v-dialog>
84
+
85
+ <!-- Preview Dialog -->
86
+ <v-dialog v-model="previewDialog" width="500" persistent>
87
+ <v-card width="100%">
88
+ <v-card-text style="max-height: 100vh; overflow-y: auto" class="pb-0">
89
+ <v-row no-gutters v-if="selectedSchool">
90
+ <v-col cols="12" class="mb-3">
91
+ <strong>Name:</strong> {{ selectedSchool.name }}
92
+ </v-col>
93
+ <v-col cols="12" class="mb-3">
94
+ <strong>Address:</strong>
95
+ {{
96
+ `${selectedSchool.address} ${
97
+ selectedSchool.continuedAddress || ""
98
+ }`
99
+ }}
100
+ {{ selectedSchool.city }} {{ selectedSchool.state }}
101
+ {{ selectedSchool.zipCode }}
102
+ </v-col>
103
+ <v-col cols="12" class="mb-3">
104
+ <strong>Region:</strong>
105
+ {{ selectedSchool.regionName || "Not assigned" }}
106
+ </v-col>
107
+ <v-col cols="12" class="mb-3">
108
+ <strong>Division:</strong>
109
+ {{ selectedSchool.divisionName || "Not assigned" }}
110
+ </v-col>
111
+ <v-col cols="12" class="mb-3">
112
+ <strong>Principal:</strong>
113
+ {{ selectedSchool.principalName || "Not assigned" }}
114
+ </v-col>
115
+ <v-col cols="12" class="mb-3">
116
+ <strong>Created:</strong>
117
+ {{
118
+ new Date(selectedSchool.createdAt || "").toLocaleDateString()
119
+ }}
120
+ </v-col>
121
+ <v-col cols="12" class="mb-3" v-if="selectedSchool.updatedAt">
122
+ <strong>Updated:</strong>
123
+ {{ new Date(selectedSchool.updatedAt).toLocaleDateString() }}
124
+ </v-col>
125
+ </v-row>
126
+ </v-card-text>
127
+
128
+ <v-toolbar class="pa-0" density="compact">
129
+ <v-row no-gutter>
130
+ <v-col cols="6" class="pa-0">
131
+ <v-btn
132
+ block
133
+ variant="text"
134
+ class="text-none"
135
+ height="48"
136
+ tile
137
+ @click="previewDialog = false"
138
+ >
139
+ Close
140
+ </v-btn>
141
+ </v-col>
142
+
143
+ <v-col cols="6" class="pa-0" v-if="canUpdate">
144
+ <v-menu>
145
+ <template #activator="{ props }">
146
+ <v-btn
147
+ block
148
+ variant="flat"
149
+ color="black"
150
+ class="text-none"
151
+ height="48"
152
+ v-bind="props"
153
+ tile
154
+ >
155
+ More actions
156
+ </v-btn>
157
+ </template>
158
+
159
+ <v-list class="pa-0">
160
+ <v-list-item
161
+ v-if="selectedSchool?.status === 'pending'"
162
+ @click="submitApproval()"
163
+ >
164
+ <v-list-item-title class="text-subtitle-2">
165
+ Approve School
166
+ </v-list-item-title>
167
+ </v-list-item>
168
+
169
+ <v-list-item @click="editFromPreview()">
170
+ <v-list-item-title class="text-subtitle-2">
171
+ Edit School
172
+ </v-list-item-title>
173
+ </v-list-item>
174
+
175
+ <v-list-item
176
+ @click="openDeleteDialog(selectedSchool?._id)"
177
+ class="text-red"
178
+ >
179
+ <v-list-item-title class="text-subtitle-2">
180
+ Delete School
181
+ </v-list-item-title>
182
+ </v-list-item>
183
+ </v-list>
184
+ </v-menu>
185
+ </v-col>
186
+ </v-row>
187
+ </v-toolbar>
188
+ </v-card>
189
+ </v-dialog>
190
+
191
+ <ConfirmDialog
192
+ v-model="confirmDialog"
193
+ :loading="deleteLoading"
194
+ @submit="handleDeleteSchool"
195
+ >
196
+ <template #title>
197
+ <span class="font-weight-medium text-h5">Delete School</span>
198
+ </template>
199
+
200
+ <template #description>
201
+ <p class="text-subtitle-2">
202
+ Are you sure you want to delete this school? This action cannot be
203
+ undone.
204
+ </p>
205
+ </template>
206
+
207
+ <template #footer>
208
+ <v-btn
209
+ variant="text"
210
+ @click="confirmDialog = false"
211
+ :disabled="deleteLoading"
212
+ class="text-none"
213
+ >
214
+ Close
215
+ </v-btn>
216
+ <v-btn
217
+ color="black"
218
+ variant="flat"
219
+ @click="handleDeleteSchool"
220
+ :loading="deleteLoading"
221
+ class="text-none"
222
+ >
223
+ Delete School
224
+ </v-btn>
225
+ </template>
226
+ </ConfirmDialog>
227
+
228
+ <Snackbar v-model="messageSnackbar" :text="message" :color="messageColor" />
229
+ </v-row>
230
+ </template>
231
+
232
+ <script setup lang="ts">
233
+ const prop = defineProps({
234
+ status: {
235
+ type: String,
236
+ default: "active",
237
+ },
238
+ org: {
239
+ type: String,
240
+ default: "",
241
+ },
242
+ app: {
243
+ type: String,
244
+ default: "school",
245
+ },
246
+ baseRoute: {
247
+ type: String,
248
+ default: "index",
249
+ },
250
+ headers: {
251
+ type: Array as PropType<Array<Record<string, any>>>,
252
+ default: () => [
253
+ {
254
+ title: "Name",
255
+ value: "name",
256
+ },
257
+ {
258
+ title: "Region",
259
+ value: "regionName",
260
+ },
261
+ {
262
+ title: "Division",
263
+ value: "divisionName",
264
+ },
265
+ {
266
+ title: "Principal",
267
+ value: "principalName",
268
+ },
269
+ ],
270
+ },
271
+ canCreate: {
272
+ type: Boolean,
273
+ default: true,
274
+ },
275
+ canUpdate: {
276
+ type: Boolean,
277
+ default: true,
278
+ },
279
+ canDelete: {
280
+ type: Boolean,
281
+ default: true,
282
+ },
283
+ });
284
+
285
+ const statusFilter = computed(() => {
286
+ const items = [
287
+ { text: "Active", params: { status: "active" } },
288
+ { text: "Pending", params: { status: "pending" } },
289
+ { text: "Suspended", params: { status: "suspended" } },
290
+ ];
291
+
292
+ if (prop.org) {
293
+ items.map((i) => ({
294
+ ...i,
295
+ params: { ...i.params, org: prop.org },
296
+ }));
297
+ }
298
+
299
+ return items;
300
+ });
301
+
302
+ const statuses = ["active", "pending", "suspended", "inactive"];
303
+ const theStatus = ref("active");
304
+
305
+ const { headerSearch } = useLocal();
306
+ const { getAll: _getSchools, approvedById } = useSchool();
307
+
308
+ const page = ref(1);
309
+ const pages = ref(0);
310
+ const pageRange = ref("-- - -- of --");
311
+
312
+ const message = ref("");
313
+ const messageSnackbar = ref(false);
314
+ const messageColor = ref("");
315
+
316
+ const items = ref<Array<Record<string, any>>>([]);
317
+
318
+ const propStatus = computed(() => prop.status);
319
+
320
+ const {
321
+ data: getSchoolReq,
322
+ refresh: getSchools,
323
+ status: getSchoolReqStatus,
324
+ } = useLazyAsyncData(
325
+ "schools-get-all-" + prop.status,
326
+ () =>
327
+ _getSchools({
328
+ page: page.value,
329
+ search: headerSearch.value,
330
+ status: prop.status,
331
+ org: prop.org,
332
+ app: prop.app,
333
+ }),
334
+ {
335
+ watch: [page, propStatus],
336
+ }
337
+ );
338
+
339
+ const loading = computed(() => getSchoolReqStatus.value === "pending");
340
+
341
+ watchEffect(() => {
342
+ if (getSchoolReq.value) {
343
+ items.value = getSchoolReq.value.items;
344
+ pages.value = getSchoolReq.value.pages;
345
+ pageRange.value = getSchoolReq.value.pageRange;
346
+ }
347
+ });
348
+
349
+ function tableRowClickHandler(_: any, data: any) {
350
+ selectedSchool.value = data.item;
351
+ previewDialog.value = true;
352
+ }
353
+
354
+ const createDialog = ref(false);
355
+ const editDialog = ref(false);
356
+ const previewDialog = ref(false);
357
+ const selectedSchool = ref<Record<string, any> | null>(null);
358
+
359
+ function successCreate() {
360
+ createDialog.value = false;
361
+ getSchools();
362
+ showMessage("School created successfully!", "success");
363
+ }
364
+
365
+ function successUpdate() {
366
+ editDialog.value = false;
367
+ previewDialog.value = false;
368
+ getSchools();
369
+ showMessage("School updated successfully!", "success");
370
+ }
371
+
372
+ function openEditDialog(school: Record<string, any>) {
373
+ selectedSchool.value = school;
374
+ editDialog.value = true;
375
+ }
376
+
377
+ function editFromPreview() {
378
+ previewDialog.value = false;
379
+ editDialog.value = true;
380
+ }
381
+
382
+ const confirmDialog = ref(false);
383
+ const selectedSchoolId = ref<string | null>(null);
384
+ const deleteLoading = ref(false);
385
+
386
+ function openDeleteDialog(id: string) {
387
+ selectedSchoolId.value = id;
388
+ confirmDialog.value = true;
389
+ if (previewDialog.value) {
390
+ previewDialog.value = false;
391
+ }
392
+ }
393
+
394
+ function showMessage(msg: string, color: string) {
395
+ message.value = msg;
396
+ messageColor.value = color;
397
+ messageSnackbar.value = true;
398
+ }
399
+
400
+ async function handleDeleteSchool() {
401
+ if (!selectedSchoolId.value) return;
402
+ deleteLoading.value = true;
403
+ try {
404
+ confirmDialog.value = false;
405
+ getSchools();
406
+ } catch (error: any) {
407
+ const errorMessage =
408
+ error?.response?._data?.message || "Failed to delete school";
409
+ showMessage(errorMessage, "error");
410
+ } finally {
411
+ deleteLoading.value = false;
412
+ selectedSchoolId.value = null;
413
+ }
414
+ }
415
+
416
+ async function submitApproval() {
417
+ try {
418
+ await approvedById(selectedSchool.value?._id ?? "");
419
+ getSchools();
420
+ } catch (error: any) {
421
+ const errorMessage =
422
+ error?.response?._data?.message || "Failed to delete school";
423
+ showMessage(errorMessage, "error");
424
+ } finally {
425
+ previewDialog.value = false;
426
+ selectedSchoolId.value = null;
427
+ }
428
+ }
429
+ </script>
@@ -8,14 +8,6 @@ export default function useLocalAuth() {
8
8
  return;
9
9
  }
10
10
 
11
- // Get access token from cookies
12
- const accessToken = useCookie("accessToken", cookieConfig).value;
13
-
14
- if (!accessToken) {
15
- // Redirect to login page if no access token
16
- navigateTo({ name: "index" });
17
- }
18
-
19
11
  const user = useCookie("user", cookieConfig).value;
20
12
 
21
13
  const { data: getCurrentUserReq, error: getCurrentUserErr } =
@@ -44,6 +36,11 @@ export default function useLocalAuth() {
44
36
  });
45
37
  }
46
38
 
39
+ function setSession({ sid = "", user = "" } = {}) {
40
+ useCookie("sid", cookieConfig).value = sid;
41
+ useCookie("user", cookieConfig).value = user;
42
+ }
43
+
47
44
  function setToken({
48
45
  refreshToken = "",
49
46
  accessToken = "",
@@ -64,18 +61,9 @@ export default function useLocalAuth() {
64
61
  }
65
62
 
66
63
  async function logout() {
67
- const refreshToken = useCookie("refreshToken", cookieConfig).value;
68
- if (refreshToken) {
69
- try {
70
- await useNuxtApp().$api(`/api/auth/logout/${refreshToken}`, {
71
- method: "DELETE",
72
- });
73
-
74
- clearCookies();
75
- } catch (error) {
76
- console.error("Logout failed:", error);
77
- }
78
- }
64
+ return useNuxtApp().$api("/api/auth/logout", {
65
+ method: "DELETE",
66
+ });
79
67
  }
80
68
 
81
69
  function getCurrentUser() {
@@ -142,6 +130,7 @@ export default function useLocalAuth() {
142
130
  return {
143
131
  authenticate,
144
132
  login,
133
+ setSession,
145
134
  logout,
146
135
  clearCookies,
147
136
  getCurrentUser,
@@ -1,44 +1,10 @@
1
- export function useLocalSetup(type: string, org?: string) {
2
- const userId = computed(() => useCookie("user").value ?? "");
3
-
4
- const { getByUserType } = useMember();
5
-
6
- const { data: userMemberData, error: userMemberError } = useLazyAsyncData(
7
- "get-member-by-id",
8
- () => getByUserType(userId.value, type, org),
9
- { watch: [userId] }
10
- );
11
-
12
- watchEffect(() => {
13
- if (userMemberError.value) {
14
- navigateTo({
15
- name: "index",
16
- });
17
- }
18
- });
19
-
20
- const { getRoleById } = useRole();
21
-
22
- const roleId = computed(() => userMemberData.value?.role ?? "");
23
-
1
+ export function useLocalSetup() {
24
2
  const userAppRole = useState<Record<string, any> | null>(
25
3
  "userAppRole",
26
4
  () => null
27
5
  );
28
6
 
29
- const { data: getRoleByIdReq } = useLazyAsyncData(
30
- "get-role-by-id",
31
- () => getRoleById(roleId.value),
32
- { watch: [roleId], immediate: false }
33
- );
34
-
35
- watchEffect(() => {
36
- if (getRoleByIdReq.value) {
37
- userAppRole.value = getRoleByIdReq.value;
38
- }
39
- });
40
-
41
- const id = computed(() => userMemberData.value?.org ?? "id");
7
+ const id = useState<string | null>("memberShipOrgId", () => null);
42
8
 
43
9
  return {
44
10
  userAppRole,