oceanhelm 0.0.1

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,348 @@
1
+ <template>
2
+ <!-- Fleet Summary -->
3
+ <div class="row mb-3">
4
+ <div class="col-md-4">
5
+ <div class="card border-0 shadow-sm">
6
+ <div class="card-body d-flex align-items-center">
7
+ <div class="rounded-circle bg-primary bg-opacity-10 p-3 me-3">
8
+ <i class="bi bi-check-circle-fill text-primary fs-4"></i>
9
+ </div>
10
+ <div>
11
+ <h6 class="mb-0">Active Vessels</h6>
12
+ <h3 class="mt-2 mb-0">{{ activeVesselsCount }}</h3>
13
+ </div>
14
+ </div>
15
+ </div>
16
+ </div>
17
+ <div class="col-md-4">
18
+ <div class="card border-0 shadow-sm">
19
+ <div class="card-body d-flex align-items-center">
20
+ <div class="rounded-circle bg-secondary bg-opacity-10 p-3 me-3">
21
+ <i class="bi bi-pause-circle-fill text-secondary fs-4"></i>
22
+ </div>
23
+ <div>
24
+ <h6 class="mb-0">Inactive</h6>
25
+ <h3 class="mt-2 mb-0">{{ inactiveVesselsCount }}</h3>
26
+ </div>
27
+ </div>
28
+ </div>
29
+ </div>
30
+ <!-- Register New Fleet -->
31
+ <div class="col-md-4" v-if="canAddVessel">
32
+ <div class="card border-0 shadow-sm" @click="handleAddVessel">
33
+ <div class="card-body d-flex align-items-center">
34
+ <div class="rounded-circle bg-success bg-opacity-10 p-3 me-3">
35
+ <i class="bi bi-patch-plus-fill text-success fs-4"></i>
36
+ </div>
37
+ <div>
38
+ <h6 class="mb-0">Add New Vessel</h6>
39
+ </div>
40
+ </div>
41
+ </div>
42
+ </div>
43
+ </div>
44
+
45
+ <h4 class="mb-4"><i class="bi bi-ship me-2"></i>Registered Vessels</h4>
46
+
47
+ <!-- Loading State -->
48
+ <div v-if="loading" class="text-center py-4">
49
+ <div class="spinner-border text-primary" role="status">
50
+ <span class="visually-hidden">Loading...</span>
51
+ </div>
52
+ <p class="mt-2">Loading vessels...</p>
53
+ </div>
54
+
55
+ <!-- Empty State -->
56
+ <div v-else-if="!vessels.length" class="alert alert-primary" role="alert">
57
+ <h4 class="alert-heading">Empty Fleet!</h4>
58
+ <p>You have an empty fleet, you have not added any vessel yet.</p>
59
+ <hr>
60
+ <p class="mb-0">Click on the add vessel button above, to start adding vessels to your fleet</p>
61
+ </div>
62
+
63
+ <!-- Vessel Cards -->
64
+ <div v-else class="row">
65
+ <div class="col-lg-6" v-for="vessel in vessels" :key="vessel.registrationNumber || vessel.id">
66
+ <div class="vessel-card" @click="handleVesselClick(vessel)">
67
+ <div class="card-body d-flex align-items-center">
68
+ <div class="vessel-icon left">
69
+ <i class="fas fa-ship"></i>
70
+ </div>
71
+ <div class="flex-grow-1">
72
+ <div class="d-flex justify-content-between align-items-center mb-2">
73
+ <h5 class="card-title mb-0">{{ vessel.name }}</h5>
74
+ <span :class="['vessel-status', statusClass(vessel.status)]">{{ vessel.status }}</span>
75
+ </div>
76
+ <div class="row">
77
+ <div class="col-6">
78
+ <small class="text-muted">Registration #:</small>
79
+ <p class="mb-0">{{ vessel.registrationNumber }}</p>
80
+ </div>
81
+ <div class="col-6">
82
+ <small class="text-muted">Next Maintenance:</small>
83
+ <p class="mb-0">{{ vessel.nextMaintenance || 'Not scheduled' }}</p>
84
+ </div>
85
+ </div>
86
+ </div>
87
+ </div>
88
+ <div class="action-icon left delete">
89
+ <i class="bi bi-trash" @click.stop="handleDeleteVessel(vessel)"></i>
90
+ </div>
91
+ <button class="btn btn-primary" @click.stop="handleToggleStatus(vessel)">
92
+ {{ vessel.status === 'Active' ? 'Mark Inactive' : 'Mark Active' }}
93
+ </button>
94
+ </div>
95
+ </div>
96
+ </div>
97
+ </template>
98
+
99
+ <script>
100
+ export default {
101
+ name: 'VesselList',
102
+
103
+ props: {
104
+ // Required props
105
+ vessels: {
106
+ type: Array,
107
+ required: true,
108
+ default: () => []
109
+ },
110
+ userProfile: {
111
+ type: Object,
112
+ required: true,
113
+ default: () => ({ role: 'viewer', vessel: null })
114
+ },
115
+
116
+ // Optional props
117
+ loading: {
118
+ type: Boolean,
119
+ default: false
120
+ },
121
+ currentRoute: {
122
+ type: String,
123
+ default: 'dashboard' // 'dashboard', 'maintenanceroute', 'crewroute'
124
+ },
125
+
126
+ // Configuration props
127
+ config: {
128
+ type: Object,
129
+ default: () => ({
130
+ enableAdd: true,
131
+ enableEdit: true,
132
+ enableDelete: true,
133
+ enableStatusToggle: true,
134
+ showCertifications: true
135
+ })
136
+ }
137
+ },
138
+
139
+ emits: [
140
+ 'vessel-add',
141
+ 'vessel-click',
142
+ 'vessel-edit',
143
+ 'vessel-delete',
144
+ 'vessel-toggle-status',
145
+ 'vessel-navigate',
146
+ 'access-denied'
147
+ ],
148
+
149
+ computed: {
150
+ activeVesselsCount() {
151
+ return this.vessels.filter(vessel => vessel.status === "Active").length;
152
+ },
153
+
154
+ inactiveVesselsCount() {
155
+ return this.vessels.filter(vessel => vessel.status === "Inactive").length;
156
+ },
157
+
158
+ canAddVessel() {
159
+ return this.config.enableAdd &&
160
+ (this.userProfile.role === 'owner' || this.userProfile.role === 'staff');
161
+ }
162
+ },
163
+
164
+ methods: {
165
+ // Event handlers that emit to parent
166
+ handleAddVessel() {
167
+ if (!this.canAddVessel) {
168
+ this.handleAccessDenied('add vessel');
169
+ return;
170
+ }
171
+ this.$emit('vessel-add');
172
+ },
173
+
174
+ handleVesselClick(vessel) {
175
+ const navigationData = {
176
+ vessel,
177
+ route: this.currentRoute,
178
+ id: vessel.registrationNumber,
179
+ name: vessel.name
180
+ };
181
+
182
+ if (this.currentRoute === 'dashboard') {
183
+ this.$emit('vessel-click', vessel);
184
+ } else {
185
+ this.$emit('vessel-navigate', navigationData);
186
+ }
187
+ },
188
+
189
+ handleDeleteVessel(vessel) {
190
+ if (!this.grantAccess(vessel)) {
191
+ this.handleAccessDenied('delete vessel');
192
+ return;
193
+ }
194
+
195
+ if (!this.config.enableDelete) {
196
+ return;
197
+ }
198
+
199
+ this.$emit('vessel-delete', vessel);
200
+ },
201
+
202
+ handleToggleStatus(vessel) {
203
+ if (!this.grantAccess(vessel)) {
204
+ this.handleAccessDenied('change vessel status');
205
+ return;
206
+ }
207
+
208
+ if (!this.config.enableStatusToggle) {
209
+ return;
210
+ }
211
+
212
+ this.$emit('vessel-toggle-status', vessel);
213
+ },
214
+
215
+ handleEditVessel(vessel) {
216
+ if (!this.grantAccess(vessel)) {
217
+ this.handleAccessDenied('edit vessel');
218
+ return;
219
+ }
220
+
221
+ if (!this.config.enableEdit) {
222
+ return;
223
+ }
224
+
225
+ this.$emit('vessel-edit', vessel);
226
+ },
227
+
228
+ handleAccessDenied(action) {
229
+ this.$emit('access-denied', { action, userProfile: this.userProfile });
230
+ },
231
+
232
+ // Utility methods (keep these in component as they're UI-related)
233
+ statusClass(status) {
234
+ const normalized = status?.toLowerCase() || '';
235
+ return `status-${normalized}`;
236
+ },
237
+
238
+ grantAccess(vessel) {
239
+ const { role, vessel: userVessel } = this.userProfile;
240
+
241
+ return role === 'owner' ||
242
+ role === 'staff' ||
243
+ (role === 'captain' && userVessel === vessel.name);
244
+ },
245
+
246
+ getDaysToExpiry(expiry_date) {
247
+ if (!expiry_date) return null;
248
+
249
+ const today = new Date();
250
+ const expiry = new Date(expiry_date);
251
+ const diffTime = expiry - today;
252
+ const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
253
+ return diffDays;
254
+ },
255
+
256
+ getExpiryClass(expiry_date) {
257
+ const days = this.getDaysToExpiry(expiry_date);
258
+ if (days === null) return '';
259
+ if (days < 30) return 'cert-critical';
260
+ if (days < 90) return 'cert-warning';
261
+ return '';
262
+ }
263
+ }
264
+ }
265
+ </script>
266
+
267
+ <style>
268
+ .left {
269
+ margin-left: 20px;
270
+ }
271
+
272
+ .vessel-card {
273
+ background: white;
274
+ border-radius: 10px;
275
+ box-shadow: 0 8px 16px rgba(0, 105, 192, 0.15);
276
+ transition: all 0.3s ease;
277
+ margin-bottom: 20px;
278
+ overflow: hidden;
279
+ border-left: 4px solid var(--accent-color);
280
+ }
281
+
282
+ .vessel-card:hover {
283
+ transform: translateY(-5px);
284
+ box-shadow: 0 12px 20px rgba(0, 105, 192, 0.2);
285
+ }
286
+
287
+ .vessel-icon {
288
+ display: inline-flex;
289
+ align-items: center;
290
+ justify-content: center;
291
+ width: 50px;
292
+ height: 50px;
293
+ background-color: #e3f2fd;
294
+ border-radius: 10px;
295
+ color: var(--accent-color);
296
+ font-size: 24px;
297
+ margin-right: 15px;
298
+ }
299
+
300
+ .black {
301
+ color: black !important;
302
+ }
303
+
304
+ .action-icon {
305
+ display: inline-flex;
306
+ align-items: center;
307
+ justify-content: center;
308
+ width: 30px;
309
+ height: 30px;
310
+ border-radius: 20px;
311
+ color: var(--accent-color);
312
+ font-size: 16px;
313
+ margin-right: 15px;
314
+ margin-top: 20px;
315
+ margin-bottom: 10px;
316
+ }
317
+
318
+ .delete {
319
+ background-color: var(--danger);
320
+ }
321
+
322
+ .edit {
323
+ background-color: var(--success);
324
+ }
325
+
326
+ .vessel-status {
327
+ display: inline-block;
328
+ padding: 5px 12px;
329
+ border-radius: 20px;
330
+ font-size: 12px;
331
+ font-weight: 600;
332
+ }
333
+
334
+ .status-active {
335
+ background-color: #e8f5e9;
336
+ color: #2e7d32;
337
+ }
338
+
339
+ .status-maintenance {
340
+ background-color: #fff8e1;
341
+ color: #f57f17;
342
+ }
343
+
344
+ .status-inactive {
345
+ background-color: #f5f5f5;
346
+ color: #757575;
347
+ }
348
+ </style>
package/src/index.js ADDED
@@ -0,0 +1,33 @@
1
+ // src/index.js - Library entry point
2
+ import ConfigurableSidebar from './components/ConfigurableSidebar.vue'
3
+ import VesselList from './components/VesselList.vue'
4
+ import DashHead from './components/DashHead.vue'
5
+ import OceanHelmMaintenance from './components/OceanHelmMaintenance.vue'
6
+ import ActivityLogs from './components/ActivityLogs.vue'
7
+ import CrewManagement from './components/CrewManagement.vue'
8
+ import { createSidebarConfig, defaultMenuItems } from './utils/sidebarConfig'
9
+ import { defaultPermissionChecker } from './utils/permissions'
10
+
11
+ // Export main components
12
+ export { ConfigurableSidebar, VesselList, DashHead, CrewManagement, ActivityLogs, OceanHelmMaintenance }
13
+
14
+ // Export utilities
15
+ export { createSidebarConfig, defaultMenuItems, defaultPermissionChecker }
16
+
17
+ // Export types for TypeScript users
18
+ export * from './types'
19
+
20
+ // Default export for plugin-style usage
21
+ export default {
22
+ install(app, options = {}) {
23
+ app.component('ConfigurableSidebar', ConfigurableSidebar)
24
+ app.component('VesselList', VesselList)
25
+ app.component('DashHead', DashHead)
26
+ app.component('ActivityLogs', ActivityLogs)
27
+ app.component('CrewManagement', CrewManagement)
28
+ app.component('DashHead', OceanHelmMaintenance)
29
+
30
+ // Provide global configuration
31
+ app.provide('sidebarConfig', options)
32
+ }
33
+ }
package/src/main.js ADDED
@@ -0,0 +1,11 @@
1
+ import './assets/main.css'
2
+
3
+ import { createApp } from 'vue'
4
+ import App from './App.vue'
5
+ import router from './router'
6
+
7
+ const app = createApp(App)
8
+
9
+ app.use(router)
10
+
11
+ app.mount('#app')
@@ -0,0 +1,8 @@
1
+ import { createRouter, createWebHistory } from 'vue-router'
2
+
3
+ const router = createRouter({
4
+ history: createWebHistory(import.meta.env.BASE_URL),
5
+ routes: [],
6
+ })
7
+
8
+ export default router
@@ -0,0 +1,33 @@
1
+ // ===== src/types/index.js (JSDoc type definitions) =====
2
+
3
+ /**
4
+ * @typedef {Object} MenuItem
5
+ * @property {'link'|'button'|'dropdown'|'text'|'separator'} type - Type of menu item
6
+ * @property {string} [label] - Display label for the item
7
+ * @property {string} [icon] - CSS class for icon
8
+ * @property {string} [href] - Link URL for 'link' type items
9
+ * @property {string} [action] - Action identifier for 'button' type items
10
+ * @property {string[]} [roles] - Required user roles to see this item
11
+ * @property {boolean} [active] - Whether item is currently active
12
+ * @property {boolean} [external] - Whether link opens in new tab
13
+ * @property {boolean} [preventDefault] - Whether to prevent default navigation
14
+ * @property {MenuItem[]} [children] - Child items for dropdown type
15
+ */
16
+
17
+ /**
18
+ * @typedef {Object} UserProfile
19
+ * @property {string} role - User's role
20
+ * @property {*} [key] - Any additional user properties
21
+ */
22
+
23
+ /**
24
+ * @typedef {Object} SidebarConfig
25
+ * @property {string} [brandName] - Brand name to display
26
+ * @property {string} [logoIcon] - CSS class for logo icon
27
+ * @property {boolean} [showLogo] - Whether to show logo section
28
+ * @property {boolean} [responsive] - Whether to enable responsive behavior
29
+ * @property {string} [customClasses] - Additional CSS classes
30
+ */
31
+
32
+ // Export empty object to make this a module
33
+ export {}
@@ -0,0 +1,10 @@
1
+ // ===== src/utils/permissions.js =====
2
+ export const defaultPermissionChecker = (item, userProfile) => {
3
+ // No role restrictions
4
+ if (!item.roles || item.roles.length === 0) {
5
+ return true
6
+ }
7
+
8
+ // Check if user role is in allowed roles
9
+ return item.roles.includes(userProfile?.role)
10
+ }
@@ -0,0 +1,87 @@
1
+ // ===== src/utils/sidebarConfig.js =====
2
+ export const createSidebarConfig = (options = {}) => {
3
+ return {
4
+ brandName: options.brandName || 'OceanHelm',
5
+ logoIcon: options.logoIcon || 'bi bi-water',
6
+ showLogo: options.showLogo !== false,
7
+ responsive: options.responsive !== false,
8
+ ...options
9
+ }
10
+ }
11
+
12
+ export const defaultMenuItems = [
13
+ {
14
+ type: 'link',
15
+ label: 'Dashboard',
16
+ icon: 'bi bi-speedometer2',
17
+ href: '/app/dashboard',
18
+ active: true
19
+ },
20
+ {
21
+ type: 'link',
22
+ label: 'Activity Log',
23
+ icon: 'bi bi-activity',
24
+ href: '/activity-log',
25
+ roles: ['owner'] // Role-based visibility
26
+ },
27
+ {
28
+ type: 'text',
29
+ label: 'Services'
30
+ },
31
+ {
32
+ type: 'button',
33
+ label: 'Maintenance',
34
+ icon: 'bi bi-tools',
35
+ action: 'maintenance'
36
+ },
37
+ {
38
+ type: 'dropdown',
39
+ label: 'Crew Management',
40
+ icon: 'bi bi-people',
41
+ children: [
42
+ {
43
+ label: 'All Crew',
44
+ action: 'crew-all',
45
+ roles: ['owner', 'staff']
46
+ },
47
+ {
48
+ type: 'separator'
49
+ },
50
+ {
51
+ label: 'Get Crew by Vessel',
52
+ action: 'crew-by-vessel'
53
+ }
54
+ ]
55
+ },
56
+ {
57
+ type: 'link',
58
+ label: 'Inventory Management',
59
+ icon: 'bi bi-clipboard-data',
60
+ href: '/app/inventory'
61
+ },
62
+ {
63
+ type: 'link',
64
+ label: 'Requisition Processing',
65
+ icon: 'bi bi-calendar-check',
66
+ href: '/app/requisition'
67
+ },
68
+ {
69
+ type: 'button',
70
+ label: 'Voyage Manager',
71
+ icon: 'fas fa-ship',
72
+ action: 'coming-soon'
73
+ },
74
+ {
75
+ type: 'button',
76
+ label: 'Settings',
77
+ icon: 'bi bi-gear',
78
+ action: 'settings',
79
+ roles: ['owner', 'staff']
80
+ },
81
+ {
82
+ type: 'button',
83
+ label: 'Help & Support',
84
+ icon: 'bi bi-question-circle',
85
+ action: 'help'
86
+ }
87
+ ]