Skip to content

Commit 8a9a3b2

Browse files
warwickschroederjasontaylordev
authored andcommitted
Initial checkin for the Platform Capabilities section on the dashboard
1 parent 340a986 commit 8a9a3b2

File tree

7 files changed

+701
-0
lines changed

7 files changed

+701
-0
lines changed
Lines changed: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
1+
<script setup lang="ts">
2+
import FAIcon from "@/components/FAIcon.vue";
3+
import { IconDefinition, faCircle } from "@fortawesome/free-solid-svg-icons";
4+
import { CapabilityStatus, StatusIndicator } from "@/components/platformcapabilities/types";
5+
6+
const props = defineProps<{
7+
status: CapabilityStatus;
8+
icon: IconDefinition;
9+
title: string;
10+
subtitle: string;
11+
helpUrl: string;
12+
dataUrl: string;
13+
description: string;
14+
indicators?: StatusIndicator[];
15+
isLoading?: boolean;
16+
}>();
17+
</script>
18+
19+
<template>
20+
<div
21+
class="capability-card"
22+
:class="{
23+
'capability-available': !props.isLoading && props.status === CapabilityStatus.Available,
24+
'capability-partially-available': !props.isLoading && props.status === CapabilityStatus.PartiallyAvailable,
25+
'capability-unavailable': !props.isLoading && props.status === CapabilityStatus.Unavailable,
26+
'capability-loading': props.isLoading,
27+
'capability-notconfigured': !props.isLoading && props.status === CapabilityStatus.NotConfigured,
28+
}"
29+
>
30+
<div v-if="props.isLoading" class="loading-overlay">
31+
<div class="loading-spinner"></div>
32+
<div class="loading-text">Loading {{ props.title }} capability status...</div>
33+
</div>
34+
<div v-if="!props.isLoading" class="capability-header">
35+
<FAIcon
36+
:icon="props.icon"
37+
class="capability-icon"
38+
:class="{
39+
'text-success': props.status === CapabilityStatus.Available,
40+
'text-warning': props.status === CapabilityStatus.PartiallyAvailable,
41+
'text-danger': props.status === CapabilityStatus.Unavailable,
42+
'text-info': props.status === CapabilityStatus.NotConfigured,
43+
}"
44+
/>
45+
<div class="capability-info">
46+
<div class="capability-title-row">
47+
<div class="capability-title">{{ props.title }}</div>
48+
<div v-if="props.indicators" class="status-indicators">
49+
<div v-for="indicator in props.indicators" :key="indicator.label" class="indicator-item" v-tippy="indicator.tooltip">
50+
<FAIcon
51+
:icon="faCircle"
52+
class="indicator-light"
53+
:class="{
54+
'light-success': indicator.status === CapabilityStatus.Available,
55+
'light-warning': indicator.status === CapabilityStatus.PartiallyAvailable,
56+
'light-danger': indicator.status === CapabilityStatus.Unavailable,
57+
}"
58+
/>
59+
<span class="indicator-label">{{ indicator.label }}</span>
60+
</div>
61+
</div>
62+
</div>
63+
<div class="capability-subtitle">{{ props.subtitle }}</div>
64+
</div>
65+
<div v-if="props.status !== CapabilityStatus.NotConfigured" class="capability-status">
66+
<span
67+
class="status-badge"
68+
:class="{
69+
'status-available': props.status === CapabilityStatus.Available,
70+
'status-partially-available': props.status === CapabilityStatus.PartiallyAvailable,
71+
'status-unavailable': props.status === CapabilityStatus.Unavailable,
72+
}"
73+
>
74+
{{ props.status }}
75+
</span>
76+
</div>
77+
</div>
78+
<div v-if="!props.isLoading" class="capability-footer">
79+
<div class="capability-description">
80+
{{ props.description }}
81+
</div>
82+
<a :href="props.status === CapabilityStatus.Available ? props.dataUrl : props.helpUrl" :target="props.status === CapabilityStatus.Available ? '_self' : '_blank'" class="btn-primary learn-more-btn">
83+
{{ props.status === CapabilityStatus.Available ? "View Data" : props.status === CapabilityStatus.NotConfigured ? "Get Started" : "Learn More" }}
84+
</a>
85+
</div>
86+
</div>
87+
</template>
88+
89+
<style scoped>
90+
.capability-card {
91+
background: var(--card-bg, #fff);
92+
border: 1px solid var(--border-color, #e0e0e0);
93+
border-radius: 8px;
94+
padding: 20px;
95+
margin-bottom: 16px;
96+
transition: all 0.2s ease;
97+
position: relative;
98+
min-height: 150px;
99+
}
100+
101+
.capability-available {
102+
border-left: 4px solid var(--success-color, #28a745);
103+
}
104+
105+
.capability-partially-available {
106+
border-left: 4px solid var(--warning-color, #ffc107);
107+
}
108+
109+
.capability-unavailable {
110+
border-left: 4px solid var(--danger-color, #dc3545);
111+
}
112+
113+
.capability-loading {
114+
border-left: 4px solid var(--border-color, #e0e0e0);
115+
}
116+
117+
.capability-notconfigured {
118+
background: linear-gradient(135deg, #f6f9fc 0%, #e9f2f9 100%);
119+
border: 1px solid #c3ddf5;
120+
border-left: 4px solid #007bff;
121+
}
122+
123+
.text-info {
124+
color: #007bff;
125+
}
126+
127+
.loading-overlay {
128+
position: absolute;
129+
top: 0;
130+
left: 0;
131+
right: 0;
132+
bottom: 0;
133+
background: rgba(255, 255, 255, 0.9);
134+
display: flex;
135+
flex-direction: column;
136+
align-items: center;
137+
justify-content: center;
138+
border-radius: 8px;
139+
z-index: 10;
140+
}
141+
142+
.loading-spinner {
143+
width: 40px;
144+
height: 40px;
145+
border: 3px solid var(--border-color, #e0e0e0);
146+
border-top-color: var(--primary-color, #007bff);
147+
border-radius: 50%;
148+
animation: spin 0.8s linear infinite;
149+
}
150+
151+
@keyframes spin {
152+
to {
153+
transform: rotate(360deg);
154+
}
155+
}
156+
157+
.loading-text {
158+
margin-top: 12px;
159+
color: var(--text-secondary, #666);
160+
font-size: 14px;
161+
}
162+
163+
.is-loading {
164+
opacity: 0.3;
165+
pointer-events: none;
166+
}
167+
168+
.capability-header {
169+
display: flex;
170+
align-items: flex-start;
171+
gap: 16px;
172+
margin-bottom: 12px;
173+
}
174+
175+
.capability-icon {
176+
font-size: 24px;
177+
flex-shrink: 0;
178+
margin-top: 2px;
179+
}
180+
181+
.capability-info {
182+
flex: 1;
183+
min-width: 0;
184+
}
185+
186+
.capability-title-row {
187+
display: flex;
188+
align-items: center;
189+
gap: 12px;
190+
margin-bottom: 4px;
191+
}
192+
193+
.capability-title {
194+
font-size: 18px;
195+
font-weight: 600;
196+
color: var(--text-primary, #333);
197+
}
198+
199+
.status-indicators {
200+
display: flex;
201+
gap: 12px;
202+
align-items: center;
203+
}
204+
205+
.indicator-item {
206+
display: flex;
207+
align-items: center;
208+
gap: 4px;
209+
cursor: help;
210+
}
211+
212+
.indicator-light {
213+
font-size: 10px;
214+
}
215+
216+
.light-success {
217+
color: var(--success-color, #28a745);
218+
}
219+
220+
.light-warning {
221+
color: var(--warning-color, #ffc107);
222+
}
223+
224+
.light-danger {
225+
color: var(--danger-color, #dc3545);
226+
}
227+
228+
.indicator-label {
229+
font-size: 12px;
230+
color: var(--text-secondary, #666);
231+
white-space: nowrap;
232+
}
233+
234+
.capability-subtitle {
235+
font-size: 14px;
236+
color: var(--text-secondary, #666);
237+
line-height: 1.4;
238+
}
239+
240+
.capability-status {
241+
display: flex;
242+
flex-direction: column;
243+
align-items: flex-end;
244+
gap: 4px;
245+
}
246+
247+
.status-badge {
248+
padding: 4px 12px;
249+
border-radius: 12px;
250+
font-size: 12px;
251+
font-weight: 600;
252+
text-transform: uppercase;
253+
letter-spacing: 0.5px;
254+
}
255+
256+
.status-available {
257+
background-color: #d4edda;
258+
color: #155724;
259+
}
260+
261+
.status-partially-available {
262+
background-color: #fff3cd;
263+
color: #856404;
264+
}
265+
266+
.status-unavailable {
267+
background-color: #f8d7da;
268+
color: #721c24;
269+
}
270+
271+
.capability-footer {
272+
display: flex;
273+
justify-content: space-between;
274+
align-items: center;
275+
gap: 16px;
276+
margin-top: 12px;
277+
padding-top: 12px;
278+
border-top: 1px solid var(--border-color, #e0e0e0);
279+
}
280+
281+
.capability-description {
282+
flex: 1;
283+
font-size: 13px;
284+
color: var(--text-secondary, #666);
285+
line-height: 1.5;
286+
overflow: hidden;
287+
text-overflow: ellipsis;
288+
display: -webkit-box;
289+
-webkit-line-clamp: 2;
290+
line-clamp: 2;
291+
-webkit-box-orient: vertical;
292+
}
293+
294+
.learn-more-btn {
295+
padding: 8px 16px;
296+
border-radius: 4px;
297+
text-decoration: none;
298+
font-size: 14px;
299+
font-weight: 500;
300+
transition: all 0.2s ease;
301+
white-space: nowrap;
302+
border: 1px solid transparent;
303+
}
304+
305+
.learn-more-btn.btn-primary {
306+
background-color: var(--primary-color, #007bff);
307+
color: white;
308+
border-color: var(--primary-color, #007bff);
309+
}
310+
311+
.learn-more-btn.btn-primary:hover {
312+
background-color: var(--primary-hover-color, #0056b3);
313+
border-color: var(--primary-hover-color, #0056b3);
314+
}
315+
316+
.learn-more-btn.btn-secondary {
317+
background-color: transparent;
318+
color: var(--primary-color, #007bff);
319+
border-color: var(--primary-color, #007bff);
320+
}
321+
322+
.learn-more-btn.btn-secondary:hover {
323+
background-color: var(--primary-color, #007bff);
324+
color: white;
325+
}
326+
327+
/* Responsive adjustments */
328+
@media (max-width: 768px) {
329+
.capability-header {
330+
flex-direction: column;
331+
}
332+
333+
.capability-status {
334+
align-items: flex-start;
335+
flex-direction: row;
336+
gap: 8px;
337+
}
338+
339+
.capability-footer {
340+
flex-direction: column;
341+
align-items: flex-start;
342+
}
343+
344+
.learn-more-btn {
345+
width: 100%;
346+
text-align: center;
347+
}
348+
}
349+
</style>

0 commit comments

Comments
 (0)