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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oceanhelm",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
4
4
  "description": "Oceanhelm's highly customizable library",
5
5
  "private": false,
6
6
  "type": "module",
@@ -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
- // Permission checker function
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
- // Check if the item itself has permission
115
- if (!this.hasPermission(item)) {
116
- return false;
117
- }
116
+ if (!this.hasPermission(item)) return false;
118
117
 
119
- // For dropdown items, check if any children have permission
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
- // Close others if you want accordion behavior
138
- this.filteredMenuItems.forEach(i => {
139
- if (i !== item && i.type === 'dropdown') {
140
- i.open = false;
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 checking
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
- // Default role-based checking
160
- if (!item.roles || item.roles.length === 0) {
161
- return true; // No role restriction
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 item.roles.includes(this.userProfile.role);
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 behavior
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
- // Clock-in/out is office-only (owner + staff roles).
62
- // Payroll and appraisal will also cover crew in a later release.
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', // Coming next
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
- /* ── end HR MODULE ── */
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', action: 'crew-all', roles: ['owner', 'staff'] },
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', action: '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',