@hostlink/nuxt-light 1.67.8 → 1.68.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.
Files changed (39) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +103 -286
  3. package/dist/runtime/components/L/AppMain.d.vue.ts +4 -4
  4. package/dist/runtime/components/L/AppMain.vue +2 -0
  5. package/dist/runtime/components/L/AppMain.vue.d.ts +4 -4
  6. package/dist/runtime/components/L/NotificationBell.vue +187 -0
  7. package/dist/runtime/components/L/Table.d.vue.ts +12 -6
  8. package/dist/runtime/components/L/Table.vue +7 -1
  9. package/dist/runtime/components/L/Table.vue.d.ts +12 -6
  10. package/dist/runtime/models/Notification.d.ts +2 -0
  11. package/dist/runtime/models/Notification.js +52 -0
  12. package/dist/runtime/models/SystemValue.js +3 -1
  13. package/dist/runtime/pages/Notification/index.vue +55 -0
  14. package/dist/runtime/pages/User/[user_id]/update-role.d.vue.ts +3 -0
  15. package/dist/runtime/pages/User/[user_id]/update-role.vue.d.ts +3 -0
  16. package/dist/runtime/pages/User/[user_id]/view.d.vue.ts +3 -0
  17. package/dist/runtime/pages/User/[user_id]/view.vue.d.ts +3 -0
  18. package/package.json +1 -1
  19. /package/dist/runtime/{pages/EventLog/_eventlog_id/view.d.vue.ts → components/L/NotificationBell.d.vue.ts} +0 -0
  20. /package/dist/runtime/{pages/EventLog/_eventlog_id/view.vue.d.ts → components/L/NotificationBell.vue.d.ts} +0 -0
  21. /package/dist/runtime/pages/{User/_user_id → EventLog/[eventlog_id]}/view.d.vue.ts +0 -0
  22. /package/dist/runtime/pages/EventLog/{_eventlog_id → [eventlog_id]}/view.vue +0 -0
  23. /package/dist/runtime/pages/{User/_user_id → EventLog/[eventlog_id]}/view.vue.d.ts +0 -0
  24. /package/dist/runtime/pages/{Role/_name/update-child.d.vue.ts → Notification/index.d.vue.ts} +0 -0
  25. /package/dist/runtime/pages/{Role/_name/update-child.vue.d.ts → Notification/index.vue.d.ts} +0 -0
  26. /package/dist/runtime/pages/{SystemValue/_systemvalue_id/edit.d.vue.ts → Role/[name]/update-child.d.vue.ts} +0 -0
  27. /package/dist/runtime/pages/Role/{_name → [name]}/update-child.vue +0 -0
  28. /package/dist/runtime/pages/{SystemValue/_systemvalue_id/edit.vue.d.ts → Role/[name]/update-child.vue.d.ts} +0 -0
  29. /package/dist/runtime/pages/{User/_user_id → SystemValue/[systemvalue_id]}/edit.d.vue.ts +0 -0
  30. /package/dist/runtime/pages/SystemValue/{_systemvalue_id → [systemvalue_id]}/edit.vue +0 -0
  31. /package/dist/runtime/pages/{User/_user_id → SystemValue/[systemvalue_id]}/edit.vue.d.ts +0 -0
  32. /package/dist/runtime/pages/User/{_user_id → [user_id]}/change-password.d.vue.ts +0 -0
  33. /package/dist/runtime/pages/User/{_user_id → [user_id]}/change-password.vue +0 -0
  34. /package/dist/runtime/pages/User/{_user_id → [user_id]}/change-password.vue.d.ts +0 -0
  35. /package/dist/runtime/pages/User/{_user_id/update-role.d.vue.ts → [user_id]/edit.d.vue.ts} +0 -0
  36. /package/dist/runtime/pages/User/{_user_id → [user_id]}/edit.vue +0 -0
  37. /package/dist/runtime/pages/User/{_user_id/update-role.vue.d.ts → [user_id]/edit.vue.d.ts} +0 -0
  38. /package/dist/runtime/pages/User/{_user_id → [user_id]}/update-role.vue +0 -0
  39. /package/dist/runtime/pages/User/{_user_id → [user_id]}/view.vue +0 -0
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "light",
3
3
  "configKey": "light",
4
- "version": "1.67.8",
4
+ "version": "1.68.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -1,283 +1,100 @@
1
1
  import { defineNuxtModule, createResolver, extendPages, addImports, addComponentsDir, addImportsDir, resolveFiles, addPluginTemplate, addPlugin } from '@nuxt/kit';
2
+ import { readdirSync } from 'node:fs';
3
+ import { basename, join, relative, dirname } from 'node:path';
2
4
 
3
- const routes = [
4
- {
5
- name: "CustomField",
6
- path: "/CustomField",
7
- file: "runtime/pages/CustomField/index.vue"
8
- },
9
- {
10
- name: "CustomField-custom_field_id-edit",
11
- path: "/CustomField/:custom_field_id/edit",
12
- file: "runtime/pages/CustomField/[custom_field_id]/edit.vue"
13
- },
14
- {
15
- name: "EventLog",
16
- path: "/EventLog",
17
- file: "runtime/pages/EventLog/index.vue"
18
- },
19
- {
20
- name: "FileManager",
21
- path: "/FileManager",
22
- file: "runtime/pages/FileManager/index.vue"
23
- },
24
- {
25
- name: "MailLog",
26
- path: "/MailLog",
27
- file: "runtime/pages/MailLog/index.vue"
28
- },
29
- {
30
- name: "Permission",
31
- path: "/Permission",
32
- file: "runtime/pages/Permission/index.vue"
33
- },
34
- {
35
- name: "Role",
36
- path: "/Role",
37
- file: "runtime/pages/Role/index.vue"
38
- },
39
- {
40
- name: "System",
41
- path: "/System",
42
- file: "runtime/pages/System/index.vue"
43
- },
44
- {
45
- name: "SystemValue",
46
- path: "/SystemValue",
47
- file: "runtime/pages/SystemValue/index.vue"
48
- },
49
- {
50
- name: "Translate",
51
- path: "/Translate",
52
- file: "runtime/pages/Translate/index.vue"
53
- },
54
- {
55
- name: "User",
56
- path: "/User",
57
- file: "runtime/pages/User/index.vue"
58
- },
59
- {
60
- name: "UserLog",
61
- path: "/UserLog",
62
- file: "runtime/pages/UserLog/index.vue"
63
- },
64
- {
65
- name: "Permission-add",
66
- path: "/Permission/add",
67
- file: "runtime/pages/Permission/add.vue"
68
- },
69
- {
70
- name: "Permission-all",
71
- path: "/Permission/all",
72
- file: "runtime/pages/Permission/all.vue"
73
- },
74
- {
75
- name: "Permission-export",
76
- path: "/Permission/export",
77
- file: "runtime/pages/Permission/export.vue"
78
- },
79
- {
80
- name: "Role-add",
81
- path: "/Role/add",
82
- file: "runtime/pages/Role/add.vue"
83
- },
84
- {
85
- name: "Role-add2",
86
- path: "/Role/add2",
87
- file: "runtime/pages/Role/add2.vue"
88
- },
89
- {
90
- name: "System-fs",
91
- path: "/System/fs",
92
- file: "runtime/pages/System/fs.vue"
93
- },
94
- {
95
- name: "System-mailtest",
96
- path: "/System/mailtest",
97
- file: "runtime/pages/System/mailtest.vue"
98
- },
99
- {
100
- name: "System-package",
101
- path: "/System/package",
102
- file: "runtime/pages/System/package.vue"
103
- },
104
- {
105
- name: "System-phpinfo",
106
- path: "/System/phpinfo",
107
- file: "runtime/pages/System/phpinfo.vue"
108
- },
109
- {
110
- name: "System-setting",
111
- path: "/System/setting",
112
- file: "runtime/pages/System/setting.vue"
113
- },
114
- {
115
- name: "System-test",
116
- path: "/System/test",
117
- file: "runtime/pages/System/test.vue"
118
- },
119
- {
120
- name: "System-view_as",
121
- path: "/System/view_as",
122
- file: "runtime/pages/System/view_as.vue"
123
- },
124
- {
125
- name: "System-menu",
126
- path: "/System/menu",
127
- file: "runtime/pages/System/menu/index.vue"
128
- },
129
- {
130
- name: "SystemValue-add",
131
- path: "/SystemValue/add",
132
- file: "runtime/pages/SystemValue/add.vue"
133
- },
134
- {
135
- name: "System-database-check",
136
- path: "/System/database/check",
137
- file: "runtime/pages/System/database/check.vue"
138
- },
139
- {
140
- name: "System-database-migrate",
141
- path: "/System/database/migrate",
142
- file: "runtime/pages/System/database/migrate.vue"
143
- },
144
- {
145
- name: "System-database-process",
146
- path: "/System/database/process",
147
- file: "runtime/pages/System/database/process.vue"
148
- },
149
- {
150
- name: "System-database-restore",
151
- path: "/System/database/restore",
152
- file: "runtime/pages/System/database/restore.vue"
153
- },
154
- {
155
- name: "System-database-backup",
156
- path: "/System/database/backup",
157
- file: "runtime/pages/System/database/backup.vue"
158
- },
159
- {
160
- name: "System-database-table",
161
- path: "/System/database/table",
162
- file: "runtime/pages/System/database/table.vue"
163
- },
164
- {
165
- name: "System-database-event",
166
- path: "/System/database/event",
167
- file: "runtime/pages/System/database/event.vue"
168
- },
169
- {
170
- name: "EventLog-eventlog_id-view",
171
- path: "/EventLog/:eventlog_id/view",
172
- file: "runtime/pages/EventLog/_eventlog_id/view.vue"
173
- },
174
- {
175
- name: "Role-name-update-child",
176
- path: "/Role/:name/update-child",
177
- file: "runtime/pages/Role/_name/update-child.vue"
178
- },
179
- {
180
- name: "SystemValue-systemvalue_id-edit",
181
- path: "/SystemValue/:systemvalue_id/edit",
182
- file: "runtime/pages/SystemValue/_systemvalue_id/edit.vue"
183
- },
184
- {
185
- name: "User-user_id-change-password",
186
- path: "/User/:user_id/change-password",
187
- file: "runtime/pages/User/_user_id/change-password.vue"
188
- },
189
- {
190
- name: "User-user_id-edit",
191
- path: "/User/:user_id/edit",
192
- file: "runtime/pages/User/_user_id/edit.vue"
193
- },
194
- {
195
- name: "User-user_id-update-role",
196
- path: "/User/:user_id/update-role",
197
- file: "runtime/pages/User/_user_id/update-role.vue"
198
- },
199
- {
200
- name: "User",
201
- path: "/User",
202
- file: "runtime/pages/User/index.vue"
203
- },
204
- {
205
- name: "User-add",
206
- path: "/User/add",
207
- file: "runtime/pages/User/add.vue"
208
- },
209
- {
210
- name: "User-user_id-view",
211
- path: "/User/:user_id/view",
212
- file: "runtime/pages/User/_user_id/view.vue"
213
- },
214
- {
215
- name: "User-profile",
216
- path: "/User/profile",
217
- file: "runtime/pages/User/profile.vue"
218
- },
219
- {
220
- name: "User-createAccessToken",
221
- path: "/User/createAccessToken",
222
- file: "runtime/pages/User/createAccessToken.vue"
223
- },
224
- {
225
- path: "/User/setting",
226
- file: "runtime/pages/User/setting.vue",
227
- children: [
228
- {
229
- name: "User-setting",
230
- path: "",
231
- file: "runtime/pages/User/setting/index.vue"
232
- },
233
- {
234
- name: "User-setting-bio-auth",
235
- path: "bio-auth",
236
- file: "runtime/pages/User/setting/bio-auth.vue"
237
- },
238
- {
239
- name: "User-setting-information",
240
- path: "information",
241
- file: "runtime/pages/User/setting/information.vue"
242
- },
243
- {
244
- name: "User-setting-favorite",
245
- path: "favorite",
246
- file: "runtime/pages/User/setting/favorite.vue"
247
- },
248
- {
249
- name: "User-setting-menu",
250
- path: "menu",
251
- file: "runtime/pages/User/setting/menu.vue"
252
- },
253
- {
254
- name: "User-setting-open_id",
255
- path: "open_id",
256
- file: "runtime/pages/User/setting/open_id.vue"
257
- },
258
- {
259
- name: "User-setting-password",
260
- path: "password",
261
- file: "runtime/pages/User/setting/password.vue"
262
- },
263
- {
264
- name: "User-setting-style",
265
- path: "style",
266
- file: "runtime/pages/User/setting/style.vue"
267
- },
268
- {
269
- name: "User-setting-two-factor-auth",
270
- path: "two-factor-auth",
271
- file: "runtime/pages/User/setting/two-factor-auth.vue"
272
- },
273
- {
274
- name: "User-setting-sessions",
275
- path: "sessions",
276
- file: "runtime/pages/User/setting/sessions.vue"
5
+ function buildPath(dirParts, fileName) {
6
+ const parts = [...dirParts];
7
+ if (fileName !== "index") {
8
+ parts.push(fileName);
9
+ }
10
+ return "/" + parts.map((p) => {
11
+ if (p.startsWith("[") && p.endsWith("]")) {
12
+ return ":" + p.slice(1, -1);
13
+ }
14
+ return p;
15
+ }).join("/");
16
+ }
17
+ function buildName(dirParts, fileName) {
18
+ const parts = [...dirParts];
19
+ if (fileName !== "index") {
20
+ parts.push(fileName);
21
+ }
22
+ return parts.map((p) => {
23
+ if (p.startsWith("[") && p.endsWith("]")) {
24
+ return p.slice(1, -1);
25
+ }
26
+ return p;
27
+ }).join("-");
28
+ }
29
+ function scanRoutes(pagesDir, dir) {
30
+ const entries = readdirSync(dir, { withFileTypes: true });
31
+ const files = entries.filter((e) => e.isFile() && e.name.endsWith(".vue")).sort((a, b) => {
32
+ if (a.name === "index.vue") return -1;
33
+ if (b.name === "index.vue") return 1;
34
+ return a.name.localeCompare(b.name);
35
+ });
36
+ const dirs = entries.filter((e) => e.isDirectory());
37
+ const result = [];
38
+ const handledDirs = /* @__PURE__ */ new Set();
39
+ for (const file of files) {
40
+ const baseName = basename(file.name, ".vue");
41
+ const siblingDir = dirs.find((d) => d.name === baseName);
42
+ if (siblingDir) {
43
+ handledDirs.add(baseName);
44
+ const filePath = join(dir, file.name);
45
+ const relPath = relative(pagesDir, filePath);
46
+ const dirParts = dirname(relPath).split("/").filter((p) => p && p !== ".");
47
+ const parentPath = buildPath(dirParts, baseName);
48
+ const children = scanRoutes(pagesDir, join(dir, baseName));
49
+ for (const child of children) {
50
+ child.path = child.path.replace(parentPath, "");
51
+ if (child.path.startsWith("/")) {
52
+ child.path = child.path.slice(1);
53
+ }
277
54
  }
278
- ]
55
+ result.push({
56
+ path: parentPath,
57
+ file: relPath,
58
+ children
59
+ });
60
+ }
61
+ }
62
+ for (const file of files) {
63
+ const baseName = basename(file.name, ".vue");
64
+ const siblingDir = dirs.find((d) => d.name === baseName);
65
+ if (!siblingDir) {
66
+ const filePath = join(dir, file.name);
67
+ const relPath = relative(pagesDir, filePath);
68
+ const dirParts = dirname(relPath).split("/").filter((p) => p && p !== ".");
69
+ result.push({
70
+ name: buildName(dirParts, baseName),
71
+ path: buildPath(dirParts, baseName),
72
+ file: relPath
73
+ });
74
+ }
279
75
  }
280
- ];
76
+ for (const d of dirs) {
77
+ if (!handledDirs.has(d.name)) {
78
+ result.push(...scanRoutes(pagesDir, join(dir, d.name)));
79
+ }
80
+ }
81
+ return result;
82
+ }
83
+ function generateRoutes(pagesDir) {
84
+ return scanRoutes(pagesDir, pagesDir);
85
+ }
86
+ function resolveRouteFiles(routes, resolve) {
87
+ return routes.map((route) => {
88
+ const resolved = {
89
+ ...route,
90
+ file: resolve(`runtime/pages/${route.file}`)
91
+ };
92
+ if (route.children) {
93
+ resolved.children = resolveRouteFiles(route.children, resolve);
94
+ }
95
+ return resolved;
96
+ });
97
+ }
281
98
 
282
99
  const module$1 = defineNuxtModule({
283
100
  meta: {
@@ -349,26 +166,26 @@ const module$1 = defineNuxtModule({
349
166
  }
350
167
  };
351
168
  nuxt.options.runtimeConfig = runtimeConfig;
169
+ const pagesDir = resolver.resolve("./runtime/pages");
170
+ const generatedRoutes = resolveRouteFiles(generateRoutes(pagesDir), resolver.resolve);
352
171
  extendPages((pages) => {
353
- for (const route of routes) {
172
+ for (const route of generatedRoutes) {
354
173
  if (route.children) {
355
174
  pages.unshift({
356
175
  path: route.path,
357
- file: resolver.resolve(route.file),
358
- children: route.children.map((child) => {
359
- return {
360
- name: child.name,
361
- path: child.path,
362
- file: resolver.resolve(child.file)
363
- };
364
- })
176
+ file: route.file,
177
+ children: route.children.map((child) => ({
178
+ name: child.name,
179
+ path: child.path,
180
+ file: child.file
181
+ }))
365
182
  });
366
183
  continue;
367
184
  }
368
185
  pages.unshift({
369
186
  name: route.name,
370
187
  path: route.path,
371
- file: resolver.resolve(route.file)
188
+ file: route.file
372
189
  });
373
190
  }
374
191
  });
@@ -1,12 +1,12 @@
1
- declare var __VLS_38: {}, __VLS_240: {}, __VLS_351: {}, __VLS_358: {};
1
+ declare var __VLS_38: {}, __VLS_245: {}, __VLS_356: {}, __VLS_363: {};
2
2
  type __VLS_Slots = {} & {
3
3
  header?: (props: typeof __VLS_38) => any;
4
4
  } & {
5
- 'user-menu'?: (props: typeof __VLS_240) => any;
5
+ 'user-menu'?: (props: typeof __VLS_245) => any;
6
6
  } & {
7
- 'page-top'?: (props: typeof __VLS_351) => any;
7
+ 'page-top'?: (props: typeof __VLS_356) => any;
8
8
  } & {
9
- 'page-bottom'?: (props: typeof __VLS_358) => any;
9
+ 'page-bottom'?: (props: typeof __VLS_363) => any;
10
10
  };
11
11
  declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
12
12
  logout: (...args: any[]) => void;
@@ -288,6 +288,8 @@ const onLogout = async () => {
288
288
  </q-menu>
289
289
  </q-btn>
290
290
 
291
+ <l-notification-bell class="q-mr-xs" />
292
+
291
293
  <div class="q-mx-sm" v-if="$q.screen.gt.xs">
292
294
  <div class="text-bold text-right">
293
295
  {{ my.first_name }} {{ my.last_name }}
@@ -1,12 +1,12 @@
1
- declare var __VLS_38: {}, __VLS_240: {}, __VLS_351: {}, __VLS_358: {};
1
+ declare var __VLS_38: {}, __VLS_245: {}, __VLS_356: {}, __VLS_363: {};
2
2
  type __VLS_Slots = {} & {
3
3
  header?: (props: typeof __VLS_38) => any;
4
4
  } & {
5
- 'user-menu'?: (props: typeof __VLS_240) => any;
5
+ 'user-menu'?: (props: typeof __VLS_245) => any;
6
6
  } & {
7
- 'page-top'?: (props: typeof __VLS_351) => any;
7
+ 'page-top'?: (props: typeof __VLS_356) => any;
8
8
  } & {
9
- 'page-bottom'?: (props: typeof __VLS_358) => any;
9
+ 'page-bottom'?: (props: typeof __VLS_363) => any;
10
10
  };
11
11
  declare const __VLS_base: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
12
12
  logout: (...args: any[]) => void;
@@ -0,0 +1,187 @@
1
+ <script setup>
2
+ import { ref, computed, onMounted, onUnmounted } from "vue";
3
+ import { useQuasar } from "quasar";
4
+ import { useLight, q, m, navigateTo } from "#imports";
5
+ const $q = useQuasar();
6
+ const light = useLight();
7
+ const notifications = ref([]);
8
+ const unreadCount = ref(0);
9
+ const loading = ref(false);
10
+ const fetchUnreadCount = async () => {
11
+ try {
12
+ const data = await q({
13
+ my: {
14
+ unreadNotificationCount: true
15
+ }
16
+ });
17
+ unreadCount.value = data.my.unreadNotificationCount || 0;
18
+ } catch {
19
+ }
20
+ };
21
+ const fetchNotifications = async () => {
22
+ loading.value = true;
23
+ try {
24
+ const data = await q({
25
+ app: {
26
+ listNotification: {
27
+ data: {
28
+ __args: {
29
+ limit: 10,
30
+ offset: 0
31
+ },
32
+ notification_id: true,
33
+ type: true,
34
+ title: true,
35
+ message: true,
36
+ link: true,
37
+ is_read: true,
38
+ created_time: true
39
+ },
40
+ meta: {
41
+ total: true,
42
+ key: true,
43
+ name: true
44
+ }
45
+ }
46
+ }
47
+ });
48
+ notifications.value = data.app.listNotification.data || [];
49
+ } catch (e) {
50
+ $q.notify({ message: e.message, color: "negative" });
51
+ } finally {
52
+ loading.value = false;
53
+ }
54
+ };
55
+ const markRead = async (notification) => {
56
+ if (notification.is_read) return;
57
+ try {
58
+ await m("markNotificationRead", { id: notification.notification_id });
59
+ notification.is_read = 1;
60
+ unreadCount.value = Math.max(0, unreadCount.value - 1);
61
+ } catch (e) {
62
+ $q.notify({ message: e.message, color: "negative" });
63
+ }
64
+ };
65
+ const markAllRead = async () => {
66
+ try {
67
+ await m("markAllNotificationsRead");
68
+ notifications.value.forEach((n) => n.is_read = 1);
69
+ unreadCount.value = 0;
70
+ } catch (e) {
71
+ $q.notify({ message: e.message, color: "negative" });
72
+ }
73
+ };
74
+ const goToInbox = () => {
75
+ navigateTo("/Notification");
76
+ };
77
+ const onClickNotification = (notification) => {
78
+ markRead(notification);
79
+ if (notification.link) {
80
+ navigateTo(notification.link);
81
+ }
82
+ };
83
+ const iconForType = (type) => {
84
+ switch (type) {
85
+ case "success":
86
+ return "sym_o_check_circle";
87
+ case "warning":
88
+ return "sym_o_warning";
89
+ case "error":
90
+ return "sym_o_error";
91
+ default:
92
+ return "sym_o_info";
93
+ }
94
+ };
95
+ const colorForType = (type) => {
96
+ switch (type) {
97
+ case "success":
98
+ return "positive";
99
+ case "warning":
100
+ return "warning";
101
+ case "error":
102
+ return "negative";
103
+ default:
104
+ return "info";
105
+ }
106
+ };
107
+ let pollInterval;
108
+ onMounted(() => {
109
+ fetchUnreadCount();
110
+ pollInterval = setInterval(fetchUnreadCount, 6e4);
111
+ });
112
+ onUnmounted(() => {
113
+ if (pollInterval) clearInterval(pollInterval);
114
+ });
115
+ const openDropdown = () => {
116
+ };
117
+ const unreadNotifications = computed(() => notifications.value.filter((n) => !n.is_read));
118
+ const readNotifications = computed(() => notifications.value.filter((n) => n.is_read));
119
+ </script>
120
+
121
+ <template>
122
+ <q-btn dense flat round icon="sym_o_notifications" class="q-mr-xs">
123
+ <q-badge v-if="unreadCount > 0" color="negative" floating rounded>{{ unreadCount > 99 ? '99+' : unreadCount }}</q-badge>
124
+ <q-menu :offset="[0, 8]" max-width="360px" max-height="480px" auto-close @show="fetchNotifications">
125
+ <q-card flat bordered>
126
+ <q-card-section class="row items-center justify-between q-py-sm">
127
+ <div class="text-subtitle2">{{ $t('Notifications') }}</div>
128
+ <q-btn v-if="unreadCount > 0" flat dense size="sm" color="primary" @click="markAllRead">
129
+ {{ $t('Mark all read') }}
130
+ </q-btn>
131
+ </q-card-section>
132
+ <q-separator />
133
+
134
+ <q-card-section v-if="loading" class="text-center q-py-md">
135
+ <q-spinner color="primary" size="2em" />
136
+ </q-card-section>
137
+
138
+ <q-list v-else-if="notifications.length" padding dense separator>
139
+ <template v-if="unreadNotifications.length">
140
+ <q-item-label header class="text-caption text-uppercase text-grey">{{ $t('Unread') }}</q-item-label>
141
+ <q-item v-for="n in unreadNotifications" :key="n.notification_id" clickable v-ripple
142
+ class="bg-blue-1" @click="onClickNotification(n)">
143
+ <q-item-section avatar top>
144
+ <q-icon :name="iconForType(n.type)" :color="colorForType(n.type)" size="24px" />
145
+ </q-item-section>
146
+ <q-item-section>
147
+ <q-item-label class="text-weight-bold">{{ n.title }}</q-item-label>
148
+ <q-item-label caption lines="2">{{ n.message }}</q-item-label>
149
+ <q-item-label caption class="text-grey">{{ n.created_time }}</q-item-label>
150
+ </q-item-section>
151
+ <q-item-section side>
152
+ <q-btn flat round dense icon="sym_o_done" size="sm" color="primary"
153
+ @click.stop="markRead(n)">
154
+ <q-tooltip>{{ $t('Mark as read') }}</q-tooltip>
155
+ </q-btn>
156
+ </q-item-section>
157
+ </q-item>
158
+ </template>
159
+
160
+ <template v-if="readNotifications.length">
161
+ <q-item-label header class="text-caption text-uppercase text-grey">{{ $t('Earlier') }}</q-item-label>
162
+ <q-item v-for="n in readNotifications" :key="n.notification_id" clickable v-ripple
163
+ @click="onClickNotification(n)">
164
+ <q-item-section avatar top>
165
+ <q-icon :name="iconForType(n.type)" color="grey" size="24px" />
166
+ </q-item-section>
167
+ <q-item-section>
168
+ <q-item-label>{{ n.title }}</q-item-label>
169
+ <q-item-label caption lines="2">{{ n.message }}</q-item-label>
170
+ <q-item-label caption class="text-grey">{{ n.created_time }}</q-item-label>
171
+ </q-item-section>
172
+ </q-item>
173
+ </template>
174
+ </q-list>
175
+
176
+ <q-card-section v-else class="text-center text-grey q-py-md">
177
+ {{ $t('No notifications') }}
178
+ </q-card-section>
179
+
180
+ <q-separator />
181
+ <q-card-actions align="center">
182
+ <q-btn flat color="primary" size="sm" @click="goToInbox">{{ $t('View all notifications') }}</q-btn>
183
+ </q-card-actions>
184
+ </q-card>
185
+ </q-menu>
186
+ </q-btn>
187
+ </template>
@@ -79,17 +79,23 @@ export interface LTableRequest {
79
79
  }) => void;
80
80
  }
81
81
  declare function requestServerInteraction(): void;
82
- declare var __VLS_114: any, __VLS_117: string, __VLS_118: any, __VLS_149: any, __VLS_152: any, __VLS_322: string, __VLS_323: any;
82
+ declare var __VLS_24: {
83
+ props: any;
84
+ }, __VLS_55: any, __VLS_119: any, __VLS_122: string, __VLS_123: any, __VLS_154: any, __VLS_157: any, __VLS_327: string, __VLS_328: any;
83
85
  type __VLS_Slots = {} & {
84
- [K in NonNullable<typeof __VLS_117>]?: (props: typeof __VLS_118) => any;
86
+ [K in NonNullable<typeof __VLS_122>]?: (props: typeof __VLS_123) => any;
85
87
  } & {
86
- [K in NonNullable<typeof __VLS_322>]?: (props: typeof __VLS_323) => any;
88
+ [K in NonNullable<typeof __VLS_327>]?: (props: typeof __VLS_328) => any;
87
89
  } & {
88
- actions?: (props: typeof __VLS_114) => any;
90
+ 'header-selection'?: (props: typeof __VLS_24) => any;
89
91
  } & {
90
- 'row-expand'?: (props: typeof __VLS_149) => any;
92
+ 'top-selection'?: (props: typeof __VLS_55) => any;
91
93
  } & {
92
- 'top-right'?: (props: typeof __VLS_152) => any;
94
+ actions?: (props: typeof __VLS_119) => any;
95
+ } & {
96
+ 'row-expand'?: (props: typeof __VLS_154) => any;
97
+ } & {
98
+ 'top-right'?: (props: typeof __VLS_157) => any;
93
99
  };
94
100
  declare const __VLS_base: import("vue").DefineComponent<LTableProps, {
95
101
  requestServerInteraction: typeof requestServerInteraction;
@@ -562,7 +562,9 @@ const hasFilters = computed(() => {
562
562
  <template #header="props">
563
563
  <q-tr :props="props">
564
564
  <q-th v-if="selection != 'none'" auto-width>
565
- <q-checkbox v-model="props.selected" :dense="table?.dense" />
565
+ <slot name="header-selection" :props="props">
566
+ <q-checkbox v-model="props.selected" :dense="table?.dense" />
567
+ </slot>
566
568
  </q-th>
567
569
  <q-th v-if="hasRowExpand" auto-width></q-th>
568
570
  <q-th v-if="hasActions" auto-width></q-th>
@@ -578,6 +580,10 @@ const hasFilters = computed(() => {
578
580
  </div>
579
581
  </template>
580
582
 
583
+ <template #top-selection="props" v-if="ss.indexOf('top-selection') >= 0">
584
+ <slot name="top-selection" v-bind="props"></slot>
585
+ </template>
586
+
581
587
  <template #top-left v-if="addComponent">
582
588
  <q-btn icon="sym_o_add" :label="$t('Add')" color="primary" @click="onAdd" outline />
583
589
  </template>
@@ -79,17 +79,23 @@ export interface LTableRequest {
79
79
  }) => void;
80
80
  }
81
81
  declare function requestServerInteraction(): void;
82
- declare var __VLS_114: any, __VLS_117: string, __VLS_118: any, __VLS_149: any, __VLS_152: any, __VLS_322: string, __VLS_323: any;
82
+ declare var __VLS_24: {
83
+ props: any;
84
+ }, __VLS_55: any, __VLS_119: any, __VLS_122: string, __VLS_123: any, __VLS_154: any, __VLS_157: any, __VLS_327: string, __VLS_328: any;
83
85
  type __VLS_Slots = {} & {
84
- [K in NonNullable<typeof __VLS_117>]?: (props: typeof __VLS_118) => any;
86
+ [K in NonNullable<typeof __VLS_122>]?: (props: typeof __VLS_123) => any;
85
87
  } & {
86
- [K in NonNullable<typeof __VLS_322>]?: (props: typeof __VLS_323) => any;
88
+ [K in NonNullable<typeof __VLS_327>]?: (props: typeof __VLS_328) => any;
87
89
  } & {
88
- actions?: (props: typeof __VLS_114) => any;
90
+ 'header-selection'?: (props: typeof __VLS_24) => any;
89
91
  } & {
90
- 'row-expand'?: (props: typeof __VLS_149) => any;
92
+ 'top-selection'?: (props: typeof __VLS_55) => any;
91
93
  } & {
92
- 'top-right'?: (props: typeof __VLS_152) => any;
94
+ actions?: (props: typeof __VLS_119) => any;
95
+ } & {
96
+ 'row-expand'?: (props: typeof __VLS_154) => any;
97
+ } & {
98
+ 'top-right'?: (props: typeof __VLS_157) => any;
93
99
  };
94
100
  declare const __VLS_base: import("vue").DefineComponent<LTableProps, {
95
101
  requestServerInteraction: typeof requestServerInteraction;
@@ -0,0 +1,2 @@
1
+ declare const _default: () => void;
2
+ export default _default;
@@ -0,0 +1,52 @@
1
+ import { defineLightModel } from "#imports";
2
+ export default defineLightModel({
3
+ name: "Notification",
4
+ dataPath: "app.listNotification",
5
+ fields: {
6
+ notification_id: {
7
+ label: "ID",
8
+ sortable: true
9
+ },
10
+ type: {
11
+ label: "Type",
12
+ sortable: true,
13
+ searchable: true,
14
+ searchType: "select",
15
+ searchOptions: [
16
+ { label: "Info", value: "info" },
17
+ { label: "Success", value: "success" },
18
+ { label: "Warning", value: "warning" },
19
+ { label: "Error", value: "error" }
20
+ ],
21
+ format: (value) => {
22
+ const map = { info: "Info", success: "Success", warning: "Warning", error: "Error" };
23
+ return map[value] || value;
24
+ }
25
+ },
26
+ title: {
27
+ label: "Title",
28
+ sortable: true,
29
+ searchable: true
30
+ },
31
+ message: {
32
+ label: "Message",
33
+ searchable: true
34
+ },
35
+ link: {
36
+ label: "Link"
37
+ },
38
+ is_read: {
39
+ label: "Read",
40
+ sortable: true,
41
+ searchable: true,
42
+ searchType: "boolean",
43
+ format: (value) => value ? "Yes" : "No"
44
+ },
45
+ created_time: {
46
+ label: "Created Time",
47
+ sortable: true,
48
+ searchable: true,
49
+ searchType: "date"
50
+ }
51
+ }
52
+ });
@@ -5,7 +5,9 @@ export default defineLightModel({
5
5
  fields: {
6
6
  name: {
7
7
  label: "Name",
8
- sortable: true
8
+ sortable: true,
9
+ searchable: true,
10
+ searchPlaceholder: "Search by name..."
9
11
  },
10
12
  value: {
11
13
  label: "Value",
@@ -0,0 +1,55 @@
1
+ <script setup>
2
+ import { ref } from "vue";
3
+ import { useQuasar } from "quasar";
4
+ import { model, m, notify } from "#imports";
5
+ const $q = useQuasar();
6
+ const tableRef = ref(null);
7
+ const selected = ref([]);
8
+ const columns = model("Notification").columns({
9
+ notification_id: true,
10
+ type: true,
11
+ title: true,
12
+ message: true,
13
+ link: true,
14
+ is_read: true,
15
+ created_time: true
16
+ });
17
+ const onBulkDelete = async () => {
18
+ if (selected.value.length === 0) return;
19
+ $q.dialog({
20
+ title: $q.lang.label?.delete || "Delete",
21
+ message: "Are you sure you want to delete " + selected.value.length + " selected notification(s)?",
22
+ cancel: true,
23
+ persistent: true,
24
+ color: "negative"
25
+ }).onOk(async () => {
26
+ try {
27
+ const ids = selected.value.map((n) => n.notification_id);
28
+ await m("deleteNotifications", { ids });
29
+ notify("Deleted " + ids.length + " notification(s)");
30
+ selected.value = [];
31
+ tableRef.value?.requestServerInteraction();
32
+ } catch (e) {
33
+ notify({ message: e.message, color: "negative" });
34
+ }
35
+ });
36
+ };
37
+ const onDelete = () => {
38
+ tableRef.value?.requestServerInteraction();
39
+ };
40
+ </script>
41
+
42
+ <template>
43
+ <l-page>
44
+ <l-table ref="tableRef" row-key="notification_id" :columns="columns"
45
+ selection="multiple" v-model:selected="selected"
46
+ @request-data="$event.loadObjects('Notification')"
47
+ :actions="['delete']"
48
+ @delete="onDelete">
49
+ <template #top-selection="{ rows }">
50
+ <q-btn flat dense color="negative" icon="sym_o_delete"
51
+ :label="'Delete (' + selected.length + ')'" @click="onBulkDelete" />
52
+ </template>
53
+ </l-table>
54
+ </l-page>
55
+ </template>
@@ -0,0 +1,3 @@
1
+ declare const _default: typeof __VLS_export;
2
+ export default _default;
3
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -0,0 +1,3 @@
1
+ declare const _default: typeof __VLS_export;
2
+ export default _default;
3
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -0,0 +1,3 @@
1
+ declare const _default: typeof __VLS_export;
2
+ export default _default;
3
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
@@ -0,0 +1,3 @@
1
+ declare const _default: typeof __VLS_export;
2
+ export default _default;
3
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hostlink/nuxt-light",
3
- "version": "1.67.8",
3
+ "version": "1.68.0",
4
4
  "description": "HostLink Nuxt Light Framework",
5
5
  "repository": {
6
6
  "type": "git",