Skip to content

Commit d3eed8c

Browse files
Fix platform capabilities panel styling and responsiveness (#2996)
* Fix platform capabilities panel styling and responsiveness * Add data-status attribute to CapabilityCard and update test helpers * remove unused platform capability icon logic * Use CSS grid for capability card layout
1 parent 74269e4 commit d3eed8c

8 files changed

Lines changed: 84 additions & 248 deletions

File tree

src/Frontend/src/components/platformcapabilities/CapabilityCard.vue

Lines changed: 66 additions & 187 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { ref, computed } from "vue";
33
import { useRouter } from "vue-router";
44
import FAIcon from "@/components/FAIcon.vue";
5-
import { type IconDefinition, faCircle, faTimes } from "@fortawesome/free-solid-svg-icons";
5+
import { faCircle, faTimes } from "@fortawesome/free-solid-svg-icons";
66
import type { StatusIndicator, WizardPage } from "@/components/platformcapabilities/types";
77
import { Capability, CapabilityStatus } from "@/components/platformcapabilities/constants";
88
import WizardDialog from "./WizardDialog.vue";
@@ -15,7 +15,6 @@ const emit = defineEmits<{
1515
1616
const props = defineProps<{
1717
status: CapabilityStatus;
18-
icon: IconDefinition;
1918
title: Capability;
2019
subtitle: string;
2120
helpButtonText: string;
@@ -32,6 +31,8 @@ const shouldShowWizard = computed(() => {
3231
return props.wizardPages && props.wizardPages.length > 0 && (props.status === CapabilityStatus.EndpointsNotConfigured || props.status === CapabilityStatus.InstanceNotConfigured);
3332
});
3433
34+
const dataStatus = computed(() => (props.isLoading ? "loading" : props.status.toLowerCase().replace(/ /g, "-")));
35+
3536
function isExternalUrl(url: string): boolean {
3637
return url.startsWith("http://") || url.startsWith("https://");
3738
}
@@ -48,73 +49,52 @@ function handleButtonClick() {
4849
</script>
4950

5051
<template>
51-
<div
52-
class="capability-card"
53-
data-testid="capability-card"
54-
:class="{
55-
'capability-available': !props.isLoading && props.status === CapabilityStatus.Available,
56-
'capability-unavailable': !props.isLoading && props.status === CapabilityStatus.Unavailable,
57-
'capability-partially-unavailable': !props.isLoading && props.status === CapabilityStatus.PartiallyUnavailable,
58-
'capability-loading': props.isLoading,
59-
'capability-notconfigured': !props.isLoading && (props.status === CapabilityStatus.EndpointsNotConfigured || props.status === CapabilityStatus.InstanceNotConfigured),
60-
}"
61-
>
52+
<div class="capability-card" data-testid="capability-card" :data-status="dataStatus">
6253
<div v-if="props.isLoading" class="loading-overlay">
6354
<div class="loading-spinner"></div>
6455
<div class="loading-text">Loading {{ props.title }} capability status...</div>
6556
</div>
66-
<button class="hide-card-btn" @click="emit('hide')" v-tippy="'Hide this card'">
67-
<FAIcon :icon="faTimes" />
68-
</button>
6957
<div v-if="!props.isLoading" class="capability-header">
70-
<FAIcon
71-
:icon="props.icon"
72-
class="capability-icon"
73-
:class="{
74-
'text-success': props.status === CapabilityStatus.Available,
75-
'text-danger': props.status === CapabilityStatus.Unavailable,
76-
'text-warning': props.status === CapabilityStatus.PartiallyUnavailable,
77-
'text-info': props.status === CapabilityStatus.EndpointsNotConfigured || props.status === CapabilityStatus.InstanceNotConfigured,
78-
}"
79-
/>
80-
<div class="capability-info">
81-
<div class="capability-title-row">
82-
<h6 class="capability-title">{{ props.title }}</h6>
83-
<div v-if="props.indicators" class="status-indicators">
84-
<div v-for="indicator in props.indicators" :key="indicator.label" class="indicator-item" data-testid="status-indicator" v-tippy="indicator.tooltip">
85-
<FAIcon
86-
:icon="faCircle"
87-
class="indicator-light"
88-
:class="{
89-
'light-success': indicator.status === CapabilityStatus.Available,
90-
'light-warning': indicator.status === CapabilityStatus.EndpointsNotConfigured || indicator.status === CapabilityStatus.PartiallyUnavailable,
91-
'light-danger': indicator.status === CapabilityStatus.Unavailable,
92-
}"
93-
/>
94-
<span class="indicator-label">{{ indicator.label }}</span>
95-
</div>
58+
<div class="capability-title-row">
59+
<h6 class="capability-title">{{ props.title }}</h6>
60+
<div v-if="props.indicators" class="status-indicators">
61+
<div v-for="indicator in props.indicators" :key="indicator.label" class="indicator-item" data-testid="status-indicator" v-tippy="indicator.tooltip">
62+
<FAIcon
63+
:icon="faCircle"
64+
class="indicator-light"
65+
:class="{
66+
'light-success': indicator.status === CapabilityStatus.Available,
67+
'light-warning': indicator.status === CapabilityStatus.EndpointsNotConfigured || indicator.status === CapabilityStatus.PartiallyUnavailable,
68+
'light-danger': indicator.status === CapabilityStatus.Unavailable,
69+
}"
70+
/>
71+
<span class="indicator-label">{{ indicator.label }}</span>
9672
</div>
9773
</div>
98-
<div class="capability-subtitle">{{ props.subtitle }}</div>
99-
</div>
100-
<div v-if="props.status !== CapabilityStatus.EndpointsNotConfigured && props.status !== CapabilityStatus.InstanceNotConfigured" class="capability-status">
101-
<span
102-
class="status-badge"
103-
:class="{
104-
'status-available': props.status === CapabilityStatus.Available,
105-
'status-unavailable': props.status === CapabilityStatus.Unavailable,
106-
'status-partially-unavailable': props.status === CapabilityStatus.PartiallyUnavailable,
107-
}"
108-
>
109-
{{ props.status }}
110-
</span>
74+
<div class="title-row-actions">
75+
<span
76+
v-if="props.status !== CapabilityStatus.EndpointsNotConfigured && props.status !== CapabilityStatus.InstanceNotConfigured"
77+
class="status-badge"
78+
:class="{
79+
'status-available': props.status === CapabilityStatus.Available,
80+
'status-unavailable': props.status === CapabilityStatus.Unavailable,
81+
'status-partially-unavailable': props.status === CapabilityStatus.PartiallyUnavailable,
82+
}"
83+
>
84+
{{ props.status }}
85+
</span>
86+
<button class="hide-card-btn" @click="emit('hide')" v-tippy="'Hide this card'">
87+
<FAIcon :icon="faTimes" />
88+
</button>
89+
</div>
11190
</div>
91+
<div class="capability-subtitle">{{ props.subtitle }}</div>
11292
</div>
11393
<div v-if="!props.isLoading" class="capability-footer">
11494
<div class="capability-description">
11595
{{ props.description }}
11696
</div>
117-
<button class="btn-primary learn-more-btn" @click="handleButtonClick">
97+
<button class="btn btn-primary" @click="handleButtonClick">
11898
{{ props.helpButtonText }}
11999
</button>
120100
</div>
@@ -125,69 +105,30 @@ function handleButtonClick() {
125105

126106
<style scoped>
127107
.capability-card {
128-
background: var(--card-bg, #fff);
129-
border: 1px solid var(--border-color, #e0e0e0);
130-
border-radius: 8px;
108+
background: #fff;
109+
border-top: 1px solid #eee;
110+
border-right: 1px solid #fff;
111+
border-bottom: 1px solid #eee;
112+
border-left: 1px solid #fff;
131113
padding: 20px;
132-
margin-bottom: 16px;
133-
transition: all 0.2s ease;
134114
position: relative;
135115
min-height: 150px;
136116
}
137117
138118
.hide-card-btn {
139-
position: absolute;
140-
top: 8px;
141-
right: 8px;
142119
background: none;
143120
border: none;
144-
color: var(--text-secondary, #999);
121+
color: #999;
145122
cursor: pointer;
146123
padding: 4px 6px;
147124
border-radius: 4px;
148-
opacity: 0;
149-
transition: all 0.2s ease;
150125
font-size: 12px;
151-
z-index: 5;
152-
}
153-
154-
.capability-card:hover .hide-card-btn {
155-
opacity: 1;
126+
flex-shrink: 0;
156127
}
157128
158129
.hide-card-btn:hover {
159-
background-color: var(--hover-bg, #f0f0f0);
160-
color: var(--text-primary, #333);
161-
}
162-
163-
.capability-available {
164-
border-left: 4px solid var(--success-color, #28a745);
165-
}
166-
167-
.capability-unavailable {
168-
border-left: 4px solid var(--danger-color, #dc3545);
169-
}
170-
171-
.capability-partially-unavailable {
172-
border-left: 4px solid var(--warning-color, #ffc107);
173-
}
174-
175-
.capability-loading {
176-
border-left: 4px solid var(--border-color, #e0e0e0);
177-
}
178-
179-
.capability-notconfigured {
180-
background: linear-gradient(135deg, #f6f9fc 0%, #e9f2f9 100%);
181-
border: 1px solid #c3ddf5;
182-
border-left: 4px solid #007bff;
183-
}
184-
185-
.text-info {
186-
color: #007bff;
187-
}
188-
189-
.text-warning {
190-
color: var(--warning-color, #ffc107);
130+
background-color: #f0f0f0;
131+
color: #333;
191132
}
192133
193134
.loading-overlay {
@@ -201,15 +142,14 @@ function handleButtonClick() {
201142
flex-direction: column;
202143
align-items: center;
203144
justify-content: center;
204-
border-radius: 8px;
205145
z-index: 10;
206146
}
207147
208148
.loading-spinner {
209149
width: 40px;
210150
height: 40px;
211-
border: 3px solid var(--border-color, #e0e0e0);
212-
border-top-color: var(--primary-color, #007bff);
151+
border: 3px solid #e0e0e0;
152+
border-top-color: var(--sp-blue);
213153
border-radius: 50%;
214154
animation: spin 0.8s linear infinite;
215155
}
@@ -222,39 +162,27 @@ function handleButtonClick() {
222162
223163
.loading-text {
224164
margin-top: 12px;
225-
color: var(--text-secondary, #666);
165+
color: #666;
226166
font-size: 14px;
227167
}
228168
229169
.capability-header {
230-
display: flex;
231-
align-items: flex-start;
232-
gap: 16px;
233170
margin-bottom: 12px;
234171
}
235172
236-
.capability-icon {
237-
font-size: 24px;
238-
flex-shrink: 0;
239-
margin-top: 2px;
240-
}
241-
242-
.capability-info {
243-
flex: 1;
244-
min-width: 0;
245-
}
246-
247173
.capability-title-row {
248174
display: flex;
249175
align-items: center;
250-
gap: 12px;
176+
flex-wrap: wrap;
177+
gap: 8px;
251178
margin-bottom: 4px;
252179
}
253180
254181
.capability-title {
255182
font-size: 18px;
256183
font-weight: 600;
257-
color: var(--text-primary, #333);
184+
color: #333;
185+
margin: 0;
258186
}
259187
260188
.status-indicators {
@@ -275,37 +203,39 @@ function handleButtonClick() {
275203
}
276204
277205
.light-success {
278-
color: var(--success-color, #28a745);
206+
color: #28a745;
279207
}
280208
281209
.light-warning {
282-
color: var(--warning-color, #ffc107);
210+
color: #ffc107;
283211
}
284212
285213
.light-danger {
286-
color: var(--danger-color, #dc3545);
214+
color: #dc3545;
287215
}
288216
289217
.indicator-label {
290218
font-size: 12px;
291-
color: var(--text-secondary, #666);
219+
color: #666;
292220
white-space: nowrap;
293221
}
294222
295223
.capability-subtitle {
296224
font-size: 14px;
297-
color: var(--text-secondary, #666);
225+
color: #666;
298226
line-height: 1.4;
299227
}
300228
301-
.capability-status {
229+
.title-row-actions {
302230
display: flex;
303-
flex-direction: column;
304-
align-items: flex-end;
305-
gap: 4px;
231+
align-items: center;
232+
gap: 8px;
233+
margin-left: auto;
234+
flex-shrink: 0;
306235
}
307236
308237
.status-badge {
238+
white-space: nowrap;
309239
padding: 4px 12px;
310240
border-radius: 12px;
311241
font-size: 12px;
@@ -332,68 +262,17 @@ function handleButtonClick() {
332262
.capability-footer {
333263
display: flex;
334264
justify-content: space-between;
335-
align-items: center;
265+
align-items: flex-start;
336266
gap: 16px;
337267
margin-top: 12px;
338268
padding-top: 12px;
339-
border-top: 1px solid var(--border-color, #e0e0e0);
269+
border-top: 1px solid #eee;
340270
}
341271
342272
.capability-description {
343273
flex: 1;
344274
font-size: 13px;
345-
color: var(--text-secondary, #666);
275+
color: #666;
346276
line-height: 1.5;
347-
overflow: hidden;
348-
text-overflow: ellipsis;
349-
display: -webkit-box;
350-
-webkit-line-clamp: 2;
351-
line-clamp: 2;
352-
-webkit-box-orient: vertical;
353-
}
354-
355-
.learn-more-btn {
356-
padding: 8px 16px;
357-
border-radius: 4px;
358-
text-decoration: none;
359-
font-size: 14px;
360-
font-weight: 500;
361-
transition: all 0.2s ease;
362-
white-space: nowrap;
363-
border: 1px solid transparent;
364-
}
365-
366-
.learn-more-btn.btn-primary {
367-
background-color: var(--primary-color, #007bff);
368-
color: white;
369-
border-color: var(--primary-color, #007bff);
370-
}
371-
372-
.learn-more-btn.btn-primary:hover {
373-
background-color: var(--primary-hover-color, #0056b3);
374-
border-color: var(--primary-hover-color, #0056b3);
375-
}
376-
377-
/* Responsive adjustments */
378-
@media (max-width: 768px) {
379-
.capability-header {
380-
flex-direction: column;
381-
}
382-
383-
.capability-status {
384-
align-items: flex-start;
385-
flex-direction: row;
386-
gap: 8px;
387-
}
388-
389-
.capability-footer {
390-
flex-direction: column;
391-
align-items: flex-start;
392-
}
393-
394-
.learn-more-btn {
395-
width: 100%;
396-
text-align: center;
397-
}
398277
}
399278
</style>

0 commit comments

Comments
 (0)