oceanhelm 0.0.7 → 0.0.8
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 +12 -7
- package/dist/oceanhelm.es.js.map +1 -1
- package/dist/oceanhelm.umd.js +1 -1
- package/dist/oceanhelm.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/components/ConfigurableSidebar.vue +208 -216
- package/src/components/CrewManagement.vue +82 -120
- package/src/components/VesselList.vue +9 -5
package/package.json
CHANGED
|
@@ -1,228 +1,220 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
2
|
+
<nav id="sidebar" :class="sidebarClasses">
|
|
3
|
+
<!-- Logo Section -->
|
|
4
|
+
<div class="logo d-flex align-items-center left" v-if="showLogo">
|
|
5
|
+
<i :class="logoIcon" class="me-2"></i>
|
|
6
|
+
<span>{{ brandName }}</span>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<!-- Navigation Items -->
|
|
10
|
+
<ul class="list-unstyled components mt-4">
|
|
11
|
+
<li v-for="(item, index) in filteredMenuItems" :key="index"
|
|
12
|
+
:class="{ 'active': item.active, 'dropdown': item.type === 'dropdown' }" @click="handleItemClick(item)">
|
|
13
|
+
<!-- Regular Link -->
|
|
14
|
+
<a v-if="item.type === 'link'" :href="item.href || '#'" @click.prevent="handleNavigation(item)">
|
|
15
|
+
<i :class="item.icon" v-if="item.icon"></i>
|
|
16
|
+
{{ item.label }}
|
|
17
|
+
</a>
|
|
18
|
+
|
|
19
|
+
<!-- Button/Action -->
|
|
20
|
+
<a v-else-if="item.type === 'button'" @click.prevent="handleAction(item)" style="cursor: pointer;">
|
|
21
|
+
<i :class="item.icon" v-if="item.icon"></i>
|
|
22
|
+
{{ item.label }}
|
|
23
|
+
</a>
|
|
24
|
+
|
|
25
|
+
<!-- Dropdown -->
|
|
26
|
+
<template v-else-if="item.type === 'dropdown'">
|
|
27
|
+
<a class="dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
|
28
|
+
<i :class="item.icon" v-if="item.icon"></i>
|
|
24
29
|
{{ item.label }}
|
|
25
30
|
</a>
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
@click.prevent="handleAction(item)"
|
|
31
|
-
style="cursor: pointer;"
|
|
32
|
-
>
|
|
33
|
-
<i :class="item.icon" v-if="item.icon"></i>
|
|
34
|
-
{{ item.label }}
|
|
35
|
-
</a>
|
|
36
|
-
|
|
37
|
-
<!-- Dropdown -->
|
|
38
|
-
<template v-else-if="item.type === 'dropdown'">
|
|
39
|
-
<a
|
|
40
|
-
class="dropdown-toggle"
|
|
41
|
-
type="button"
|
|
42
|
-
data-bs-toggle="dropdown"
|
|
43
|
-
aria-expanded="false"
|
|
44
|
-
>
|
|
45
|
-
<i :class="item.icon" v-if="item.icon"></i>
|
|
46
|
-
{{ item.label }}
|
|
31
|
+
<div class="dropdown-menu" aria-labelledby="dropdownMenuLink">
|
|
32
|
+
<a v-for="(subItem, subIndex) in getFilteredChildren(item)" :key="subIndex" class="dropdown-item black"
|
|
33
|
+
@click.prevent="handleAction(subItem)" style="cursor: pointer;">
|
|
34
|
+
{{ subItem.label }}
|
|
47
35
|
</a>
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
<slot name="footer"></slot>
|
|
76
|
-
</nav>
|
|
77
|
-
</template>
|
|
78
|
-
|
|
79
|
-
<script>
|
|
80
|
-
export default {
|
|
81
|
-
name: 'ConfigurableSidebar',
|
|
82
|
-
|
|
83
|
-
props: {
|
|
84
|
-
// Brand configuration
|
|
85
|
-
brandName: {
|
|
86
|
-
type: String,
|
|
87
|
-
default: 'OceanHelm'
|
|
88
|
-
},
|
|
89
|
-
logoIcon: {
|
|
90
|
-
type: String,
|
|
91
|
-
default: 'bi bi-water'
|
|
92
|
-
},
|
|
93
|
-
showLogo: {
|
|
94
|
-
type: Boolean,
|
|
95
|
-
default: true
|
|
96
|
-
},
|
|
97
|
-
|
|
98
|
-
// Menu items configuration
|
|
99
|
-
menuItems: {
|
|
100
|
-
type: Array,
|
|
101
|
-
required: true,
|
|
102
|
-
default: () => []
|
|
103
|
-
},
|
|
104
|
-
|
|
105
|
-
// User profile for role-based filtering
|
|
106
|
-
userProfile: {
|
|
107
|
-
type: Object,
|
|
108
|
-
default: () => ({})
|
|
109
|
-
},
|
|
110
|
-
|
|
111
|
-
// Sidebar behavior
|
|
112
|
-
responsive: {
|
|
113
|
-
type: Boolean,
|
|
114
|
-
default: true
|
|
115
|
-
},
|
|
116
|
-
|
|
117
|
-
// Custom classes
|
|
118
|
-
customClasses: {
|
|
119
|
-
type: String,
|
|
120
|
-
default: ''
|
|
121
|
-
},
|
|
122
|
-
|
|
123
|
-
// Permission checker function
|
|
124
|
-
permissionChecker: {
|
|
125
|
-
type: Function,
|
|
126
|
-
default: null
|
|
127
|
-
}
|
|
36
|
+
</div>
|
|
37
|
+
</template>
|
|
38
|
+
|
|
39
|
+
<!-- Separator -->
|
|
40
|
+
<div v-else-if="item.type === 'separator'" class="dropdown-divider"></div>
|
|
41
|
+
|
|
42
|
+
<!-- Static Text -->
|
|
43
|
+
<a v-else-if="item.type === 'text'">
|
|
44
|
+
{{ item.label }}
|
|
45
|
+
</a>
|
|
46
|
+
</li>
|
|
47
|
+
</ul>
|
|
48
|
+
|
|
49
|
+
<!-- Custom Slot for Additional Content -->
|
|
50
|
+
<slot name="footer"></slot>
|
|
51
|
+
</nav>
|
|
52
|
+
</template>
|
|
53
|
+
|
|
54
|
+
<script>
|
|
55
|
+
export default {
|
|
56
|
+
name: 'ConfigurableSidebar',
|
|
57
|
+
|
|
58
|
+
props: {
|
|
59
|
+
// Brand configuration
|
|
60
|
+
brandName: {
|
|
61
|
+
type: String,
|
|
62
|
+
default: 'OceanHelm'
|
|
128
63
|
},
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
return this.customClasses;
|
|
133
|
-
},
|
|
134
|
-
|
|
135
|
-
filteredMenuItems() {
|
|
136
|
-
return this.menuItems.filter(item => this.hasPermission(item));
|
|
137
|
-
}
|
|
64
|
+
logoIcon: {
|
|
65
|
+
type: String,
|
|
66
|
+
default: 'bi bi-water'
|
|
138
67
|
},
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
this.initializeResponsiveBehavior();
|
|
143
|
-
}
|
|
68
|
+
showLogo: {
|
|
69
|
+
type: Boolean,
|
|
70
|
+
default: true
|
|
144
71
|
},
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
72
|
+
|
|
73
|
+
// Menu items configuration
|
|
74
|
+
menuItems: {
|
|
75
|
+
type: Array,
|
|
76
|
+
required: true,
|
|
77
|
+
default: () => []
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
// User profile for role-based filtering
|
|
81
|
+
userProfile: {
|
|
82
|
+
type: Object,
|
|
83
|
+
default: () => ({})
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
// Sidebar behavior
|
|
87
|
+
responsive: {
|
|
88
|
+
type: Boolean,
|
|
89
|
+
default: true
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
// Custom classes
|
|
93
|
+
customClasses: {
|
|
94
|
+
type: String,
|
|
95
|
+
default: ''
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
// Permission checker function
|
|
99
|
+
permissionChecker: {
|
|
100
|
+
type: Function,
|
|
101
|
+
default: null
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
computed: {
|
|
106
|
+
sidebarClasses() {
|
|
107
|
+
return this.customClasses;
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
filteredMenuItems() {
|
|
111
|
+
return this.menuItems.filter(item => {
|
|
112
|
+
// Check if the item itself has permission
|
|
113
|
+
if (!this.hasPermission(item)) {
|
|
114
|
+
return false;
|
|
173
115
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
this.$emit('action', item);
|
|
179
|
-
},
|
|
180
|
-
|
|
181
|
-
// Item click handling
|
|
182
|
-
handleItemClick(item) {
|
|
183
|
-
this.$emit('item-click', item);
|
|
184
|
-
|
|
185
|
-
if (item.type === 'dropdown') {
|
|
186
|
-
// Handle dropdown specific logic if needed
|
|
116
|
+
|
|
117
|
+
// For dropdown items, check if any children have permission
|
|
118
|
+
if (item.type === 'dropdown' && item.children) {
|
|
119
|
+
return item.children.some(child => this.hasPermission(child));
|
|
187
120
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
121
|
+
|
|
122
|
+
return true;
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
mounted() {
|
|
128
|
+
if (this.responsive) {
|
|
129
|
+
this.initializeResponsiveBehavior();
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
methods: {
|
|
134
|
+
// Get filtered children for dropdown items
|
|
135
|
+
getFilteredChildren(item) {
|
|
136
|
+
if (!item.children) return [];
|
|
137
|
+
return item.children.filter(child => this.hasPermission(child));
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
// Permission checking
|
|
141
|
+
hasPermission(item) {
|
|
142
|
+
// Use custom permission checker if provided
|
|
143
|
+
if (this.permissionChecker) {
|
|
144
|
+
return this.permissionChecker(item, this.userProfile);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Default role-based checking
|
|
148
|
+
if (!item.roles || item.roles.length === 0) {
|
|
149
|
+
return true; // No role restriction
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return item.roles.includes(this.userProfile.role);
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
// Navigation handling
|
|
156
|
+
handleNavigation(item) {
|
|
157
|
+
this.$emit('navigate', item);
|
|
158
|
+
|
|
159
|
+
// Default behavior if no custom handler
|
|
160
|
+
if (item.href && !item.preventDefault) {
|
|
161
|
+
if (item.external) {
|
|
162
|
+
window.open(item.href, '_blank');
|
|
163
|
+
} else {
|
|
164
|
+
this.$router?.push(item.href);
|
|
202
165
|
}
|
|
203
|
-
|
|
204
|
-
// Toggle functionality
|
|
205
|
-
sidebarToggle.addEventListener('click', () => {
|
|
206
|
-
sidebar.classList.toggle('active');
|
|
207
|
-
content.classList.toggle('active');
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
// Close sidebar on outside click (mobile)
|
|
211
|
-
document.addEventListener('click', (event) => {
|
|
212
|
-
const isClickInsideSidebar = sidebar.contains(event.target);
|
|
213
|
-
const isClickOnToggleBtn = sidebarToggle.contains(event.target);
|
|
214
|
-
|
|
215
|
-
if (!isClickInsideSidebar && !isClickOnToggleBtn &&
|
|
216
|
-
window.innerWidth < 768 &&
|
|
217
|
-
sidebar.classList.contains('active')) {
|
|
218
|
-
sidebar.classList.remove('active');
|
|
219
|
-
content.classList.remove('active');
|
|
220
|
-
}
|
|
221
|
-
});
|
|
222
166
|
}
|
|
223
167
|
},
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
168
|
+
|
|
169
|
+
// Action handling
|
|
170
|
+
handleAction(item) {
|
|
171
|
+
this.$emit('action', item);
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
// Item click handling
|
|
175
|
+
handleItemClick(item) {
|
|
176
|
+
this.$emit('item-click', item);
|
|
177
|
+
|
|
178
|
+
if (item.type === 'dropdown') {
|
|
179
|
+
// Handle dropdown specific logic if needed
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
// Responsive behavior
|
|
184
|
+
initializeResponsiveBehavior() {
|
|
185
|
+
const sidebarToggle = document.getElementById('sidebarToggle');
|
|
186
|
+
const sidebar = document.getElementById('sidebar');
|
|
187
|
+
const content = document.getElementById('content');
|
|
188
|
+
|
|
189
|
+
if (!sidebarToggle || !sidebar || !content) return;
|
|
190
|
+
|
|
191
|
+
// Initial state for desktop
|
|
192
|
+
if (window.innerWidth >= 768) {
|
|
193
|
+
sidebar.classList.toggle('active');
|
|
194
|
+
content.classList.toggle('active');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Toggle functionality
|
|
198
|
+
sidebarToggle.addEventListener('click', () => {
|
|
199
|
+
sidebar.classList.toggle('active');
|
|
200
|
+
content.classList.toggle('active');
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Close sidebar on outside click (mobile)
|
|
204
|
+
document.addEventListener('click', (event) => {
|
|
205
|
+
const isClickInsideSidebar = sidebar.contains(event.target);
|
|
206
|
+
const isClickOnToggleBtn = sidebarToggle.contains(event.target);
|
|
207
|
+
|
|
208
|
+
if (!isClickInsideSidebar && !isClickOnToggleBtn &&
|
|
209
|
+
window.innerWidth < 768 &&
|
|
210
|
+
sidebar.classList.contains('active')) {
|
|
211
|
+
sidebar.classList.remove('active');
|
|
212
|
+
content.classList.remove('active');
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
emits: ['navigate', 'action', 'item-click']
|
|
219
|
+
}
|
|
220
|
+
</script>
|