fix(dashboard): Add missing CollapsibleCard shared component
This component was imported in DashboardView.vue but never committed. CollapsibleCard provides an expandable/collapsible card UI pattern used for Trezorerie, Cash Flow, Clienți, and Furnizori dashboard cards. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
242
src/shared/components/CollapsibleCard.vue
Normal file
242
src/shared/components/CollapsibleCard.vue
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
<template>
|
||||||
|
<div class="collapsible-card" :class="{ 'collapsible-card--expanded': isExpanded }">
|
||||||
|
<!-- Collapsed Header (always visible) -->
|
||||||
|
<div class="collapsible-card__header" @click="toggleExpanded" role="button" tabindex="0" @keydown.enter="toggleExpanded">
|
||||||
|
<div class="collapsible-card__content">
|
||||||
|
<i v-if="icon" :class="icon" class="collapsible-card__icon"></i>
|
||||||
|
<span class="collapsible-card__label">{{ label }}</span>
|
||||||
|
<span class="collapsible-card__value" :class="valueClass">{{ formattedValue }}</span>
|
||||||
|
</div>
|
||||||
|
<i class="pi pi-chevron-down collapsible-card__chevron"
|
||||||
|
:class="{ 'collapsible-card__chevron--expanded': isExpanded }"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Expandable Content -->
|
||||||
|
<div v-show="isExpanded" class="collapsible-card__body">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
/**
|
||||||
|
* Label displayed in the collapsed header
|
||||||
|
*/
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Main value displayed in the collapsed header
|
||||||
|
* Can be a number or string
|
||||||
|
*/
|
||||||
|
value: {
|
||||||
|
type: [Number, String],
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* PrimeVue icon class (e.g., 'pi pi-wallet')
|
||||||
|
*/
|
||||||
|
icon: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* CSS class to apply to the value (e.g., 'positive', 'negative', 'neutral')
|
||||||
|
*/
|
||||||
|
valueClass: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Whether the card is expanded by default
|
||||||
|
*/
|
||||||
|
defaultExpanded: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Format the value as currency (RON)
|
||||||
|
*/
|
||||||
|
formatCurrency: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// State
|
||||||
|
const isExpanded = ref(props.defaultExpanded)
|
||||||
|
|
||||||
|
// Computed: Format the value
|
||||||
|
const formattedValue = computed(() => {
|
||||||
|
if (props.value === null || props.value === undefined) return '-'
|
||||||
|
|
||||||
|
if (props.formatCurrency && typeof props.value === 'number') {
|
||||||
|
return new Intl.NumberFormat('ro-RO', {
|
||||||
|
style: 'currency',
|
||||||
|
currency: 'RON',
|
||||||
|
minimumFractionDigits: 0,
|
||||||
|
maximumFractionDigits: 0
|
||||||
|
}).format(props.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return String(props.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
const toggleExpanded = () => {
|
||||||
|
isExpanded.value = !isExpanded.value
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* Collapsible Card Container */
|
||||||
|
.collapsible-card {
|
||||||
|
background: var(--surface-card);
|
||||||
|
border: 1px solid var(--surface-border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
overflow: hidden;
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header - clickable area */
|
||||||
|
.collapsible-card__header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: var(--space-md) var(--space-lg);
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
transition: background-color var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible-card__header:hover {
|
||||||
|
background: var(--surface-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible-card__header:focus {
|
||||||
|
outline: 2px solid var(--color-primary);
|
||||||
|
outline-offset: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header content: icon + label + value */
|
||||||
|
.collapsible-card__content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Icon */
|
||||||
|
.collapsible-card__icon {
|
||||||
|
font-size: var(--text-lg);
|
||||||
|
color: var(--color-primary);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Label */
|
||||||
|
.collapsible-card__label {
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
color: var(--text-color-secondary);
|
||||||
|
font-weight: var(--font-medium);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Value */
|
||||||
|
.collapsible-card__value {
|
||||||
|
font-size: var(--text-lg);
|
||||||
|
font-weight: var(--font-bold);
|
||||||
|
color: var(--text-color);
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
margin-left: auto;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Value variants */
|
||||||
|
.collapsible-card__value.positive {
|
||||||
|
color: var(--green-600);
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible-card__value.negative {
|
||||||
|
color: var(--red-600);
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible-card__value.neutral {
|
||||||
|
color: var(--text-color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode value colors */
|
||||||
|
[data-theme="dark"] .collapsible-card__value.positive {
|
||||||
|
color: var(--green-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .collapsible-card__value.negative {
|
||||||
|
color: var(--red-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root:not([data-theme]) .collapsible-card__value.positive {
|
||||||
|
color: var(--green-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root:not([data-theme]) .collapsible-card__value.negative {
|
||||||
|
color: var(--red-400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Chevron */
|
||||||
|
.collapsible-card__chevron {
|
||||||
|
font-size: var(--text-lg);
|
||||||
|
color: var(--text-color-secondary);
|
||||||
|
transition: transform var(--transition-fast);
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible-card__chevron--expanded {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Body - expandable content */
|
||||||
|
.collapsible-card__body {
|
||||||
|
border-top: 1px solid var(--surface-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* When expanded, remove internal card borders/shadows */
|
||||||
|
.collapsible-card--expanded .collapsible-card__body :deep(.metric-card),
|
||||||
|
.collapsible-card--expanded .collapsible-card__body :deep(.treasury-dual-card),
|
||||||
|
.collapsible-card--expanded .collapsible-card__body :deep(.cash-flow-metric-card),
|
||||||
|
.collapsible-card--expanded .collapsible-card__body :deep(.clienti-balance-card),
|
||||||
|
.collapsible-card--expanded .collapsible-card__body :deep(.furnizori-balance-card) {
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.collapsible-card__header {
|
||||||
|
padding: var(--space-sm) var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible-card__label {
|
||||||
|
font-size: var(--text-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible-card__value {
|
||||||
|
font-size: var(--text-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible-card__icon {
|
||||||
|
font-size: var(--text-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.collapsible-card__chevron {
|
||||||
|
font-size: var(--text-base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user