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