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.
@@ -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
- <div class="col-6">
82
- <small class="text-muted">Next Maintenance:</small>
83
- <p class="mb-0">{{ vessel.nextMaintenance || 'Not scheduled' }}</p>
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
- (this.userProfile.role === 'owner' || this.userProfile.role === 'staff');
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.currentRoute === 'dashboard') {
183
- this.$emit('vessel-click', vessel);
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.$emit('vessel-navigate', navigationData);
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
- role === 'staff' ||
243
- (role === 'captain' && userVessel === vessel.name);
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;
@@ -71,6 +71,12 @@ export const defaultMenuItems = [
71
71
  icon: 'fas fa-ship',
72
72
  action: 'coming-soon'
73
73
  },
74
+ {
75
+ type: 'button',
76
+ label: 'Vessel Log',
77
+ icon: 'bi bi-file-ruled',
78
+ action: 'vessel-log'
79
+ },
74
80
  {
75
81
  type: 'button',
76
82
  label: 'Settings',