oceanhelm 0.0.6 → 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 +527 -486
- 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 +92 -41
- package/src/utils/sidebarConfig.js +6 -0
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
</div>
|
|
44
44
|
|
|
45
45
|
<h4 class="mb-4"><i class="bi bi-ship me-2"></i>Registered Vessels</h4>
|
|
46
|
-
|
|
46
|
+
|
|
47
47
|
<!-- Loading State -->
|
|
48
48
|
<div v-if="loading" class="text-center py-4">
|
|
49
49
|
<div class="spinner-border text-primary" role="status">
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
</div>
|
|
52
52
|
<p class="mt-2">Loading vessels...</p>
|
|
53
53
|
</div>
|
|
54
|
-
|
|
54
|
+
|
|
55
55
|
<!-- Empty State -->
|
|
56
56
|
<div v-else-if="!vessels.length" class="alert alert-primary" role="alert">
|
|
57
57
|
<h4 class="alert-heading">Empty Fleet!</h4>
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
<hr>
|
|
60
60
|
<p class="mb-0">Click on the add vessel button above, to start adding vessels to your fleet</p>
|
|
61
61
|
</div>
|
|
62
|
-
|
|
62
|
+
|
|
63
63
|
<!-- Vessel Cards -->
|
|
64
64
|
<div v-else class="row">
|
|
65
65
|
<div class="col-lg-6" v-for="vessel in vessels" :key="vessel.registrationNumber || vessel.id">
|
|
@@ -78,9 +78,24 @@
|
|
|
78
78
|
<small class="text-muted">Registration #:</small>
|
|
79
79
|
<p class="mb-0">{{ vessel.registrationNumber }}</p>
|
|
80
80
|
</div>
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
<
|
|
81
|
+
|
|
82
|
+
<div class="col-6" v-if="vessel.date">
|
|
83
|
+
<template v-if="vessel.status === 'Active'">
|
|
84
|
+
<small class="text-muted">Active Duration:</small>
|
|
85
|
+
<p class="mb-0">{{ getDuration(vessel.date, vessel.registrationNumber) }}</p>
|
|
86
|
+
</template>
|
|
87
|
+
<template v-else>
|
|
88
|
+
<small class="text-muted">Inactive Duration:</small>
|
|
89
|
+
<p class="mb-0">{{ getDuration(vessel.date, vessel.registrationNumber) }}</p>
|
|
90
|
+
</template>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<div class="col-6" v-else>
|
|
94
|
+
<small class="text-muted">{{ vessel.status }} Duration:</small>
|
|
95
|
+
<button type="button" class="btn btn-outline-primary btn-sm mt-1"
|
|
96
|
+
@click.stop="setVesselDate(vessel.registrationNumber)">
|
|
97
|
+
Set Date
|
|
98
|
+
</button>
|
|
84
99
|
</div>
|
|
85
100
|
</div>
|
|
86
101
|
</div>
|
|
@@ -99,7 +114,7 @@
|
|
|
99
114
|
<script>
|
|
100
115
|
export default {
|
|
101
116
|
name: 'VesselList',
|
|
102
|
-
|
|
117
|
+
|
|
103
118
|
props: {
|
|
104
119
|
// Required props
|
|
105
120
|
vessels: {
|
|
@@ -112,7 +127,7 @@ export default {
|
|
|
112
127
|
required: true,
|
|
113
128
|
default: () => ({ role: 'viewer', vessel: null })
|
|
114
129
|
},
|
|
115
|
-
|
|
130
|
+
|
|
116
131
|
// Optional props
|
|
117
132
|
loading: {
|
|
118
133
|
type: Boolean,
|
|
@@ -122,7 +137,7 @@ export default {
|
|
|
122
137
|
type: String,
|
|
123
138
|
default: 'dashboard' // 'dashboard', 'maintenanceroute', 'crewroute'
|
|
124
139
|
},
|
|
125
|
-
|
|
140
|
+
|
|
126
141
|
// Configuration props
|
|
127
142
|
config: {
|
|
128
143
|
type: Object,
|
|
@@ -135,32 +150,33 @@ export default {
|
|
|
135
150
|
})
|
|
136
151
|
}
|
|
137
152
|
},
|
|
138
|
-
|
|
153
|
+
|
|
139
154
|
emits: [
|
|
140
155
|
'vessel-add',
|
|
141
|
-
'vessel-click',
|
|
156
|
+
'vessel-click',
|
|
142
157
|
'vessel-edit',
|
|
143
158
|
'vessel-delete',
|
|
144
159
|
'vessel-toggle-status',
|
|
145
160
|
'vessel-navigate',
|
|
146
|
-
'access-denied'
|
|
161
|
+
'access-denied',
|
|
162
|
+
'date-change'
|
|
147
163
|
],
|
|
148
|
-
|
|
164
|
+
|
|
149
165
|
computed: {
|
|
150
166
|
activeVesselsCount() {
|
|
151
167
|
return this.vessels.filter(vessel => vessel.status === "Active").length;
|
|
152
168
|
},
|
|
153
|
-
|
|
169
|
+
|
|
154
170
|
inactiveVesselsCount() {
|
|
155
171
|
return this.vessels.filter(vessel => vessel.status === "Inactive").length;
|
|
156
172
|
},
|
|
157
|
-
|
|
173
|
+
|
|
158
174
|
canAddVessel() {
|
|
159
|
-
return this.config.enableAdd &&
|
|
160
|
-
|
|
175
|
+
return this.config.enableAdd &&
|
|
176
|
+
(this.userProfile.role === 'owner' || this.userProfile.role === 'staff');
|
|
161
177
|
}
|
|
162
178
|
},
|
|
163
|
-
|
|
179
|
+
|
|
164
180
|
methods: {
|
|
165
181
|
// Event handlers that emit to parent
|
|
166
182
|
handleAddVessel() {
|
|
@@ -170,7 +186,7 @@ export default {
|
|
|
170
186
|
}
|
|
171
187
|
this.$emit('vessel-add');
|
|
172
188
|
},
|
|
173
|
-
|
|
189
|
+
|
|
174
190
|
handleVesselClick(vessel) {
|
|
175
191
|
const navigationData = {
|
|
176
192
|
vessel,
|
|
@@ -178,74 +194,109 @@ export default {
|
|
|
178
194
|
id: vessel.registrationNumber,
|
|
179
195
|
name: vessel.name
|
|
180
196
|
};
|
|
181
|
-
|
|
182
|
-
if (this.
|
|
183
|
-
this
|
|
197
|
+
|
|
198
|
+
if (this.grantAccess(vessel)) {
|
|
199
|
+
if (this.currentRoute === 'dashboard') {
|
|
200
|
+
this.$emit('vessel-click', vessel);
|
|
201
|
+
} else {
|
|
202
|
+
this.$emit('vessel-navigate', navigationData);
|
|
203
|
+
}
|
|
184
204
|
} else {
|
|
185
|
-
this
|
|
205
|
+
this.handleAccessDenied('this protected route');
|
|
186
206
|
}
|
|
187
207
|
},
|
|
188
|
-
|
|
208
|
+
|
|
189
209
|
handleDeleteVessel(vessel) {
|
|
190
210
|
if (!this.grantAccess(vessel)) {
|
|
191
211
|
this.handleAccessDenied('delete vessel');
|
|
192
212
|
return;
|
|
193
213
|
}
|
|
194
|
-
|
|
214
|
+
|
|
195
215
|
if (!this.config.enableDelete) {
|
|
196
216
|
return;
|
|
197
217
|
}
|
|
198
|
-
|
|
218
|
+
|
|
199
219
|
this.$emit('vessel-delete', vessel);
|
|
200
220
|
},
|
|
201
|
-
|
|
221
|
+
|
|
202
222
|
handleToggleStatus(vessel) {
|
|
203
223
|
if (!this.grantAccess(vessel)) {
|
|
204
224
|
this.handleAccessDenied('change vessel status');
|
|
205
225
|
return;
|
|
206
226
|
}
|
|
207
|
-
|
|
227
|
+
|
|
208
228
|
if (!this.config.enableStatusToggle) {
|
|
209
229
|
return;
|
|
210
230
|
}
|
|
211
|
-
|
|
231
|
+
|
|
212
232
|
this.$emit('vessel-toggle-status', vessel);
|
|
213
233
|
},
|
|
214
|
-
|
|
234
|
+
|
|
215
235
|
handleEditVessel(vessel) {
|
|
216
236
|
if (!this.grantAccess(vessel)) {
|
|
217
237
|
this.handleAccessDenied('edit vessel');
|
|
218
238
|
return;
|
|
219
239
|
}
|
|
220
|
-
|
|
240
|
+
|
|
221
241
|
if (!this.config.enableEdit) {
|
|
222
242
|
return;
|
|
223
243
|
}
|
|
224
|
-
|
|
244
|
+
|
|
225
245
|
this.$emit('vessel-edit', vessel);
|
|
226
246
|
},
|
|
227
|
-
|
|
247
|
+
|
|
228
248
|
handleAccessDenied(action) {
|
|
229
249
|
this.$emit('access-denied', { action, userProfile: this.userProfile });
|
|
230
250
|
},
|
|
231
|
-
|
|
251
|
+
|
|
232
252
|
// Utility methods (keep these in component as they're UI-related)
|
|
233
253
|
statusClass(status) {
|
|
234
254
|
const normalized = status?.toLowerCase() || '';
|
|
235
255
|
return `status-${normalized}`;
|
|
236
256
|
},
|
|
237
|
-
|
|
257
|
+
|
|
258
|
+
getDuration(date, registrationNumber) {
|
|
259
|
+
const now = new Date();
|
|
260
|
+
const inputDate = new Date(date);
|
|
261
|
+
|
|
262
|
+
// difference in milliseconds
|
|
263
|
+
const diffMs = now - inputDate;
|
|
264
|
+
|
|
265
|
+
if (diffMs < 0) return "Invalid (future date)";
|
|
266
|
+
|
|
267
|
+
// convert to hours
|
|
268
|
+
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
|
|
269
|
+
|
|
270
|
+
const days = Math.floor(diffHours / 24);
|
|
271
|
+
const hours = diffHours % 24;
|
|
272
|
+
|
|
273
|
+
let result = "";
|
|
274
|
+
|
|
275
|
+
if (days > 0) {
|
|
276
|
+
result += `${days} day${days > 1 ? "s" : ""}`;
|
|
277
|
+
}
|
|
278
|
+
if (hours > 0) {
|
|
279
|
+
result += (result ? " " : "") + `${hours} hr${hours > 1 ? "s" : ""}`;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return result || '0 hr'
|
|
283
|
+
},
|
|
284
|
+
|
|
285
|
+
setVesselDate(registrationNumber) {
|
|
286
|
+
this.$emit('date-change', registrationNumber);
|
|
287
|
+
},
|
|
288
|
+
|
|
238
289
|
grantAccess(vessel) {
|
|
239
290
|
const { role, vessel: userVessel } = this.userProfile;
|
|
240
|
-
|
|
241
|
-
return role === 'owner' ||
|
|
242
|
-
|
|
243
|
-
|
|
291
|
+
|
|
292
|
+
return role === 'owner' ||
|
|
293
|
+
role === 'staff' ||
|
|
294
|
+
(role === 'captain' && userVessel === vessel.name);
|
|
244
295
|
},
|
|
245
|
-
|
|
296
|
+
|
|
246
297
|
getDaysToExpiry(expiry_date) {
|
|
247
298
|
if (!expiry_date) return null;
|
|
248
|
-
|
|
299
|
+
|
|
249
300
|
const today = new Date();
|
|
250
301
|
const expiry = new Date(expiry_date);
|
|
251
302
|
const diffTime = expiry - today;
|