oceanhelm 0.0.13 → 0.0.15
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/dist/oceanhelm.es.js +140 -101
- package/dist/oceanhelm.es.js.map +1 -1
- package/dist/oceanhelm.umd.js +4 -4
- package/dist/oceanhelm.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/components/ConfigurableSidebar.vue +44 -34
- package/src/utils/sidebarConfig.js +52 -27
package/package.json
CHANGED
|
@@ -80,6 +80,7 @@ export default {
|
|
|
80
80
|
},
|
|
81
81
|
|
|
82
82
|
// User profile for role-based filtering
|
|
83
|
+
// Expected shape: { role: 'staff', categories: ['hr', 'purchaser'] }
|
|
83
84
|
userProfile: {
|
|
84
85
|
type: Object,
|
|
85
86
|
default: () => ({})
|
|
@@ -97,7 +98,8 @@ export default {
|
|
|
97
98
|
default: ''
|
|
98
99
|
},
|
|
99
100
|
|
|
100
|
-
//
|
|
101
|
+
// Optional override: provide your own permission function
|
|
102
|
+
// Signature: (item, userProfile) => Boolean
|
|
101
103
|
permissionChecker: {
|
|
102
104
|
type: Function,
|
|
103
105
|
default: null
|
|
@@ -111,12 +113,9 @@ export default {
|
|
|
111
113
|
|
|
112
114
|
filteredMenuItems() {
|
|
113
115
|
return this.menuItems.filter(item => {
|
|
114
|
-
|
|
115
|
-
if (!this.hasPermission(item)) {
|
|
116
|
-
return false;
|
|
117
|
-
}
|
|
116
|
+
if (!this.hasPermission(item)) return false;
|
|
118
117
|
|
|
119
|
-
// For
|
|
118
|
+
// For dropdowns, only show if at least one child is permitted
|
|
120
119
|
if (item.type === 'dropdown' && item.children) {
|
|
121
120
|
return item.children.some(child => this.hasPermission(child));
|
|
122
121
|
}
|
|
@@ -134,41 +133,63 @@ export default {
|
|
|
134
133
|
|
|
135
134
|
methods: {
|
|
136
135
|
toggleDropdown(item) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
136
|
+
// Accordion behaviour — close others
|
|
137
|
+
this.filteredMenuItems.forEach(i => {
|
|
138
|
+
if (i !== item && i.type === 'dropdown') {
|
|
139
|
+
i.open = false;
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
item.open = !item.open;
|
|
143
|
+
},
|
|
143
144
|
|
|
144
|
-
item.open = !item.open;
|
|
145
|
-
},
|
|
146
|
-
// Get filtered children for dropdown items
|
|
147
145
|
getFilteredChildren(item) {
|
|
148
146
|
if (!item.children) return [];
|
|
149
147
|
return item.children.filter(child => this.hasPermission(child));
|
|
150
148
|
},
|
|
151
149
|
|
|
152
|
-
// Permission
|
|
150
|
+
// ─── Permission logic ────────────────────────────────────────────────────
|
|
151
|
+
// Rule summary:
|
|
152
|
+
// 1. Use permissionChecker prop if supplied.
|
|
153
|
+
// 2. No `roles` array on the item → visible to everyone.
|
|
154
|
+
// 3. User's role must be in item.roles.
|
|
155
|
+
// 4. If the item also has a `categories` array AND the user's role is
|
|
156
|
+
// NOT one of the "elevated" roles (owner, super-staff), the user must
|
|
157
|
+
// possess at least one of the listed categories.
|
|
158
|
+
// Elevated roles bypass the category gate entirely.
|
|
159
|
+
// ────────────────────────────────────────────────────────────────────────
|
|
153
160
|
hasPermission(item) {
|
|
154
|
-
// Use custom permission checker if provided
|
|
155
161
|
if (this.permissionChecker) {
|
|
156
162
|
return this.permissionChecker(item, this.userProfile);
|
|
157
163
|
}
|
|
158
164
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
165
|
+
const userRole = this.userProfile?.role || '';
|
|
166
|
+
const userCategories = this.userProfile?.categories || [];
|
|
167
|
+
|
|
168
|
+
// No role restriction — always visible
|
|
169
|
+
if (!item.roles || item.roles.length === 0) return true;
|
|
170
|
+
|
|
171
|
+
// Role must be listed
|
|
172
|
+
if (!item.roles.includes(userRole)) return false;
|
|
173
|
+
|
|
174
|
+
// Category gate — only applies when the item declares categories
|
|
175
|
+
// AND the user is not an elevated role that bypasses it
|
|
176
|
+
if (item.categories && item.categories.length > 0) {
|
|
177
|
+
const elevatedRoles = ['owner', 'super-staff'];
|
|
178
|
+
if (!elevatedRoles.includes(userRole)) {
|
|
179
|
+
// Regular staff: must hold at least one of the required categories
|
|
180
|
+
const hasCategory = item.categories.some(cat => userCategories.includes(cat));
|
|
181
|
+
if (!hasCategory) return false;
|
|
182
|
+
}
|
|
183
|
+
// Elevated roles fall through — no category check needed
|
|
162
184
|
}
|
|
163
185
|
|
|
164
|
-
return
|
|
186
|
+
return true;
|
|
165
187
|
},
|
|
166
188
|
|
|
167
189
|
// Navigation handling
|
|
168
190
|
handleNavigation(item) {
|
|
169
191
|
this.$emit('navigate', item);
|
|
170
192
|
|
|
171
|
-
// Default behavior if no custom handler
|
|
172
193
|
if (item.href && !item.preventDefault) {
|
|
173
194
|
if (item.external) {
|
|
174
195
|
window.open(item.href, '_blank');
|
|
@@ -186,13 +207,9 @@ export default {
|
|
|
186
207
|
// Item click handling
|
|
187
208
|
handleItemClick(item) {
|
|
188
209
|
this.$emit('item-click', item);
|
|
189
|
-
|
|
190
|
-
if (item.type === 'dropdown') {
|
|
191
|
-
// Handle dropdown specific logic if needed
|
|
192
|
-
}
|
|
193
210
|
},
|
|
194
211
|
|
|
195
|
-
// Responsive
|
|
212
|
+
// Responsive behaviour
|
|
196
213
|
initializeResponsiveBehavior() {
|
|
197
214
|
const sidebarToggle = document.getElementById('sidebarToggle');
|
|
198
215
|
const sidebar = document.getElementById('sidebar');
|
|
@@ -200,19 +217,16 @@ export default {
|
|
|
200
217
|
|
|
201
218
|
if (!sidebarToggle || !sidebar || !content) return;
|
|
202
219
|
|
|
203
|
-
// Initial state for desktop
|
|
204
220
|
if (window.innerWidth >= 768) {
|
|
205
221
|
sidebar.classList.toggle('active');
|
|
206
222
|
content.classList.toggle('active');
|
|
207
223
|
}
|
|
208
224
|
|
|
209
|
-
// Toggle functionality
|
|
210
225
|
sidebarToggle.addEventListener('click', () => {
|
|
211
226
|
sidebar.classList.toggle('active');
|
|
212
227
|
content.classList.toggle('active');
|
|
213
228
|
});
|
|
214
229
|
|
|
215
|
-
// Close sidebar on outside click (mobile)
|
|
216
230
|
document.addEventListener('click', (event) => {
|
|
217
231
|
const isClickInsideSidebar = sidebar.contains(event.target);
|
|
218
232
|
const isClickOnToggleBtn = sidebarToggle.contains(event.target);
|
|
@@ -232,14 +246,12 @@ export default {
|
|
|
232
246
|
</script>
|
|
233
247
|
|
|
234
248
|
<style>
|
|
235
|
-
/* Dropdown container */
|
|
236
249
|
.sidebar-dropdown {
|
|
237
250
|
list-style: none;
|
|
238
251
|
padding-left: 20px;
|
|
239
252
|
margin: 5px 0 10px 0;
|
|
240
253
|
}
|
|
241
254
|
|
|
242
|
-
/* Dropdown items */
|
|
243
255
|
.sidebar-dropdown li a {
|
|
244
256
|
display: block;
|
|
245
257
|
padding: 8px 15px;
|
|
@@ -253,7 +265,6 @@ export default {
|
|
|
253
265
|
background: rgba(255, 255, 255, 0.1);
|
|
254
266
|
}
|
|
255
267
|
|
|
256
|
-
/* Dropdown arrow */
|
|
257
268
|
.dropdown-arrow {
|
|
258
269
|
float: right;
|
|
259
270
|
transition: transform 0.2s ease;
|
|
@@ -262,5 +273,4 @@ export default {
|
|
|
262
273
|
.dropdown-arrow.open {
|
|
263
274
|
transform: rotate(180deg);
|
|
264
275
|
}
|
|
265
|
-
|
|
266
276
|
</style>
|
|
@@ -9,12 +9,14 @@ export const createSidebarConfig = (options = {}) => {
|
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
// Roles that behave like "owner" in terms of broad visibility
|
|
13
|
+
// (super-staff gets everything owner sees, minus Activity Log and approvals)
|
|
12
14
|
export const defaultMenuItems = [
|
|
13
15
|
{
|
|
14
16
|
type: 'link',
|
|
15
17
|
label: 'Home',
|
|
16
18
|
icon: 'bi bi-house',
|
|
17
|
-
roles: ['owner', 'staff', 'captain'],
|
|
19
|
+
roles: ['owner', 'super-staff', 'staff', 'captain'],
|
|
18
20
|
href: '/app/dashboard',
|
|
19
21
|
active: true
|
|
20
22
|
},
|
|
@@ -26,6 +28,7 @@ export const defaultMenuItems = [
|
|
|
26
28
|
roles: ['crew']
|
|
27
29
|
},
|
|
28
30
|
{
|
|
31
|
+
// Activity Log — intentionally excluded from super-staff
|
|
29
32
|
type: 'link',
|
|
30
33
|
label: 'Activity Log',
|
|
31
34
|
icon: 'bi bi-activity',
|
|
@@ -37,7 +40,7 @@ export const defaultMenuItems = [
|
|
|
37
40
|
label: 'People Manager',
|
|
38
41
|
icon: 'bi bi-person-plus',
|
|
39
42
|
href: '/app/people-manager',
|
|
40
|
-
roles: ['owner']
|
|
43
|
+
roles: ['owner', 'super-staff']
|
|
41
44
|
},
|
|
42
45
|
{
|
|
43
46
|
type: 'text',
|
|
@@ -46,7 +49,7 @@ export const defaultMenuItems = [
|
|
|
46
49
|
{
|
|
47
50
|
type: 'dropdown',
|
|
48
51
|
label: 'Compliance',
|
|
49
|
-
roles: ['owner', 'staff', 'captain'],
|
|
52
|
+
roles: ['owner', 'super-staff', 'staff', 'captain'],
|
|
50
53
|
icon: 'bi bi-list-ul',
|
|
51
54
|
children: [
|
|
52
55
|
{ label: 'Vessel Certification', action: 'vessel-cert' },
|
|
@@ -58,67 +61,89 @@ export const defaultMenuItems = [
|
|
|
58
61
|
},
|
|
59
62
|
{
|
|
60
63
|
// ── HR MODULE ──────────────────────────────────────────────────────────
|
|
61
|
-
//
|
|
62
|
-
//
|
|
64
|
+
// Visible to owner, super-staff, and staff with the 'hr' category.
|
|
65
|
+
// Category-based filtering is handled by the sidebar's permissionChecker.
|
|
63
66
|
type: 'dropdown',
|
|
64
67
|
label: 'HR',
|
|
65
68
|
icon: 'bi bi-people-fill',
|
|
66
|
-
roles: ['owner', 'staff'],
|
|
69
|
+
roles: ['owner', 'super-staff', 'staff'],
|
|
70
|
+
categories: ['hr'], // staff must also have this category
|
|
67
71
|
children: [
|
|
68
72
|
{
|
|
69
|
-
label: 'Attendance',
|
|
73
|
+
label: 'Attendance',
|
|
70
74
|
action: 'hr-attendance',
|
|
71
|
-
roles: ['owner', 'staff'],
|
|
75
|
+
roles: ['owner', 'super-staff', 'staff'],
|
|
76
|
+
categories: ['hr'],
|
|
72
77
|
},
|
|
73
78
|
{
|
|
74
|
-
label: 'Payroll',
|
|
79
|
+
label: 'Payroll',
|
|
75
80
|
action: 'hr-payroll',
|
|
76
|
-
roles: ['owner', 'staff'],
|
|
81
|
+
roles: ['owner', 'super-staff', 'staff'],
|
|
82
|
+
categories: ['hr'],
|
|
77
83
|
},
|
|
78
84
|
{
|
|
79
|
-
label: 'Appraisals',
|
|
85
|
+
label: 'Appraisals',
|
|
80
86
|
action: 'hr-appraisals',
|
|
81
|
-
roles: ['owner', 'staff'],
|
|
87
|
+
roles: ['owner', 'super-staff', 'staff'],
|
|
88
|
+
categories: ['hr'],
|
|
82
89
|
},
|
|
83
90
|
{
|
|
84
91
|
label: 'Leave Management',
|
|
85
92
|
action: 'hr-leave',
|
|
86
|
-
roles: ['owner', 'staff'],
|
|
93
|
+
roles: ['owner', 'super-staff', 'staff'],
|
|
94
|
+
categories: ['hr'],
|
|
87
95
|
},
|
|
88
96
|
{
|
|
89
|
-
label: 'Disciplinary Log',
|
|
97
|
+
label: 'Disciplinary Log',
|
|
90
98
|
action: 'hr-disciplinary',
|
|
91
|
-
roles: ['owner'],
|
|
99
|
+
roles: ['owner'], // owner-only, no category gate needed
|
|
92
100
|
},
|
|
93
101
|
]
|
|
94
102
|
},
|
|
95
|
-
|
|
103
|
+
{
|
|
104
|
+
// ── RECRUITMENT MODULE ─────────────────────────────────────────────────
|
|
105
|
+
// Accessible to owner, super-staff, and staff with the 'hr' category.
|
|
106
|
+
type: 'dropdown',
|
|
107
|
+
label: 'Recruitment',
|
|
108
|
+
icon: 'bi bi-person-fill-add',
|
|
109
|
+
roles: ['owner', 'super-staff', 'staff'],
|
|
110
|
+
categories: ['hr'], // staff must also have this category
|
|
111
|
+
children: [
|
|
112
|
+
{
|
|
113
|
+
label: 'Manage Recruitment',
|
|
114
|
+
action: 'recruitment-builder',
|
|
115
|
+
roles: ['owner', 'super-staff', 'staff'],
|
|
116
|
+
categories: ['hr'],
|
|
117
|
+
}
|
|
118
|
+
]
|
|
119
|
+
},
|
|
120
|
+
/* ── end HR / RECRUITMENT MODULES ── */
|
|
96
121
|
{
|
|
97
122
|
type: 'button',
|
|
98
123
|
label: 'Maintenance',
|
|
99
124
|
icon: 'bi bi-tools',
|
|
100
125
|
action: 'maintenance',
|
|
101
|
-
roles: ['owner', 'staff', 'captain']
|
|
126
|
+
roles: ['owner', 'super-staff', 'staff', 'captain']
|
|
102
127
|
},
|
|
103
128
|
{
|
|
104
129
|
type: 'dropdown',
|
|
105
130
|
label: 'Crew Management',
|
|
106
131
|
icon: 'bi bi-people',
|
|
107
|
-
roles: ['owner', 'staff', 'captain'],
|
|
132
|
+
roles: ['owner', 'super-staff', 'staff', 'captain'],
|
|
108
133
|
children: [
|
|
109
|
-
{ label: 'All Crew',
|
|
134
|
+
{ label: 'All Crew', action: 'crew-all', roles: ['owner', 'super-staff', 'staff'] },
|
|
110
135
|
{ label: 'Get Crew by Vessel', action: 'crew-by-vessel' },
|
|
111
|
-
{ label: 'Schedule',
|
|
136
|
+
{ label: 'Schedule', action: 'schedule' },
|
|
112
137
|
]
|
|
113
138
|
},
|
|
114
139
|
{
|
|
115
140
|
type: 'dropdown',
|
|
116
141
|
label: 'Inventory Management',
|
|
117
142
|
icon: 'bi bi-clipboard-data',
|
|
118
|
-
roles: ['owner', 'staff', 'captain'],
|
|
143
|
+
roles: ['owner', 'super-staff', 'staff', 'captain'],
|
|
119
144
|
children: [
|
|
120
|
-
{ label: 'Parts Inventory', action: 'inventory', roles: ['owner', 'staff', 'captain'] },
|
|
121
|
-
{ label: 'Bunkering', action: 'fuel', roles: ['owner', 'staff', 'captain'] },
|
|
145
|
+
{ label: 'Parts Inventory', action: 'inventory', roles: ['owner', 'super-staff', 'staff', 'captain'] },
|
|
146
|
+
{ label: 'Bunkering', action: 'fuel', roles: ['owner', 'super-staff', 'staff', 'captain'] },
|
|
122
147
|
]
|
|
123
148
|
},
|
|
124
149
|
{
|
|
@@ -126,21 +151,21 @@ export const defaultMenuItems = [
|
|
|
126
151
|
label: 'Requisition Processing',
|
|
127
152
|
icon: 'bi bi-calendar-check',
|
|
128
153
|
href: '/app/requisition',
|
|
129
|
-
roles: ['owner', 'staff', 'captain']
|
|
154
|
+
roles: ['owner', 'super-staff', 'staff', 'captain']
|
|
130
155
|
},
|
|
131
156
|
{
|
|
132
157
|
type: 'button',
|
|
133
158
|
label: 'Voyage Manager',
|
|
134
159
|
icon: 'fas fa-ship',
|
|
135
160
|
action: 'coming-soon',
|
|
136
|
-
roles: ['owner', 'staff', 'captain']
|
|
161
|
+
roles: ['owner', 'super-staff', 'staff', 'captain']
|
|
137
162
|
},
|
|
138
163
|
{
|
|
139
164
|
type: 'button',
|
|
140
165
|
label: 'Vessel Log',
|
|
141
166
|
icon: 'bi bi-file-ruled',
|
|
142
167
|
action: 'coming-soon',
|
|
143
|
-
roles: ['owner', 'staff', 'captain']
|
|
168
|
+
roles: ['owner', 'super-staff', 'staff', 'captain']
|
|
144
169
|
},
|
|
145
170
|
{
|
|
146
171
|
type: 'button',
|
|
@@ -161,7 +186,7 @@ export const defaultMenuItems = [
|
|
|
161
186
|
label: 'Settings',
|
|
162
187
|
icon: 'bi bi-gear',
|
|
163
188
|
action: 'settings',
|
|
164
|
-
roles: ['owner', 'staff']
|
|
189
|
+
roles: ['owner', 'super-staff', 'staff']
|
|
165
190
|
},
|
|
166
191
|
{
|
|
167
192
|
type: 'button',
|