refactor: Remove duplicate CSS and use shared patterns in views

- Move DataTable striped rows and hover styling to global primevue-overrides.css
- Add .summary-stats-inline pattern to shared stats.css
- Refactor BankCashRegisterView.vue: CSS reduced from ~210 to 45 lines (78%)
- Refactor TrialBalanceView.vue: CSS reduced from ~100 to 42 lines (58%)
- Remove :deep() violations by using global DataTable overrides
- Update templates to use shared classes (.table-empty, .loading-state, .form-actions)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-08 17:04:42 +02:00
parent 615593eb40
commit dae6db4b1a
4 changed files with 223 additions and 306 deletions

View File

@@ -433,6 +433,55 @@
}
}
/* ===== Summary Stats Inline ===== */
/* Compact horizontal stats display for page summaries */
.summary-stats-inline {
display: flex;
gap: var(--space-xl);
justify-content: flex-end;
margin-bottom: var(--space-md);
padding: var(--space-sm) var(--space-md);
background-color: var(--color-bg-secondary);
border-radius: var(--radius-md);
border: 1px solid var(--color-border);
}
.summary-stats-inline .stat-item {
display: flex;
align-items: center;
gap: var(--space-sm);
}
.summary-stats-inline .stat-label {
font-size: var(--text-sm);
color: var(--color-text-secondary);
}
.summary-stats-inline .stat-value {
font-size: var(--text-sm);
font-weight: var(--font-semibold);
font-variant-numeric: tabular-nums;
}
.summary-stats-inline .stat-value.positive,
.summary-stats-inline .stat-value.incasari {
color: var(--color-success);
}
.summary-stats-inline .stat-value.negative,
.summary-stats-inline .stat-value.plati {
color: var(--color-error);
}
/* Responsive: Stack on mobile */
@media (max-width: 768px) {
.summary-stats-inline {
flex-direction: column;
gap: var(--space-sm);
align-items: flex-end;
}
}
/* Print Styles */
@media print {
.stats-card {
@@ -445,4 +494,9 @@
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
}
.summary-stats-inline {
border: 1px solid #ccc;
background: #f5f5f5;
}
}

View File

@@ -80,8 +80,34 @@
transition: background-color var(--transition-fast) !important;
}
/* DataTable Striped Rows - Global Pattern */
.p-datatable .p-datatable-tbody > tr:nth-child(odd) {
background-color: #ffffff !important;
}
.p-datatable .p-datatable-tbody > tr:nth-child(even) {
background-color: #f8f9fa !important;
}
.p-datatable .p-datatable-tbody > tr:hover {
background: var(--color-bg-secondary, #f8fafc) !important;
background-color: #e3f2fd !important;
cursor: pointer;
}
/* Compact DataTable variant (p-datatable-sm) */
.p-datatable-sm .p-datatable-thead > tr > th {
padding: 0.5rem 0.75rem !important;
font-weight: 600 !important;
white-space: nowrap !important;
}
.p-datatable-sm .p-datatable-tbody > tr > td {
padding: 0.4rem 0.75rem !important;
}
/* DataTable font size for compact tables */
.p-datatable-sm {
font-size: 0.875rem !important;
}
/* ===== Card ===== */

View File

@@ -17,7 +17,8 @@
<template #content>
<div class="company-selection">
<p class="text-color-secondary mb-3">
Selectați o companie pentru a vizualiza registrul de casă și bancă:
Selectați o companie pentru a vizualiza registrul de casă și
bancă:
</p>
<Dropdown
v-model="selectedCompanyId"
@@ -61,7 +62,9 @@
:showClear="true"
class="w-full"
@change="handleFilterChange"
:disabled="!filters.registerType || bankAccountOptions.length === 0"
:disabled="
!filters.registerType || bankAccountOptions.length === 0
"
/>
</div>
</div>
@@ -103,7 +106,7 @@
</div>
<!-- Separate action buttons row -->
<div class="filters-actions">
<div class="form-actions">
<Button
icon="pi pi-filter-slash"
label="Resetează Filtre"
@@ -136,18 +139,26 @@
</Card>
<!-- Summary Stats - Compact, right aligned -->
<div v-if="companyStore.selectedCompany" class="summary-stats-compact">
<div v-if="companyStore.selectedCompany" class="summary-stats-inline">
<div class="stat-item">
<span class="stat-label">Total Încasări:</span>
<span class="stat-value incasari">{{ formatCurrency(treasuryStore.totals.total_incasari) }}</span>
<span class="stat-value incasari">{{
formatCurrency(treasuryStore.totals.total_incasari)
}}</span>
</div>
<div class="stat-item">
<span class="stat-label">Total Plăți:</span>
<span class="stat-value plati">{{ formatCurrency(treasuryStore.totals.total_plati) }}</span>
<span class="stat-value plati">{{
formatCurrency(treasuryStore.totals.total_plati)
}}</span>
</div>
<div class="stat-item">
<span class="stat-label">Total Sold:</span>
<span class="stat-value" :class="totalSold >= 0 ? 'incasari' : 'plati'">{{ formatCurrency(totalSold) }}</span>
<span
class="stat-value"
:class="totalSold >= 0 ? 'incasari' : 'plati'"
>{{ formatCurrency(totalSold) }}</span
>
</div>
</div>
@@ -171,14 +182,16 @@
:rowClass="getRowClass"
>
<template #empty>
<div class="no-data">
<i class="pi pi-info-circle"></i>
<p>Nu au fost găsite înregistrări</p>
<div class="table-empty">
<i class="pi pi-info-circle table-empty-icon"></i>
<p class="table-empty-message">
Nu au fost găsite înregistrări
</p>
</div>
</template>
<template #loading>
<div class="loading-table">
<div class="loading-state">
<ProgressSpinner />
<p>Se încarcă registrul...</p>
</div>
@@ -190,10 +203,31 @@
</template>
</Column>
<Column field="nract" header="Nr." sortable class="col-nr" />
<Column field="nume_cont_bancar" :header="contColumnHeader" sortable class="col-cont" />
<Column field="nume" header="Partener" sortable class="col-partener" />
<Column v-if="isValutaType" field="valuta" header="Valuta" sortable class="col-valuta" />
<Column field="incasari" header="Încasări" sortable class="col-numeric">
<Column
field="nume_cont_bancar"
:header="contColumnHeader"
sortable
class="col-cont"
/>
<Column
field="nume"
header="Partener"
sortable
class="col-partener"
/>
<Column
v-if="isValutaType"
field="valuta"
header="Valuta"
sortable
class="col-valuta"
/>
<Column
field="incasari"
header="Încasări"
sortable
class="col-numeric"
>
<template #body="slotProps">
<span class="numeric-value" v-if="slotProps.data.incasari > 0">
{{ formatNumber(slotProps.data.incasari) }}
@@ -209,14 +243,26 @@
<span class="numeric-value zero" v-else>0,00</span>
</template>
</Column>
<Column field="sold" header="Sold Cumulat" sortable class="col-numeric col-sold">
<Column
field="sold"
header="Sold Cumulat"
sortable
class="col-numeric col-sold"
>
<template #body="slotProps">
<span class="numeric-value" :class="{ negative: slotProps.data.sold < 0 }">
<span
class="numeric-value"
:class="{ negative: slotProps.data.sold < 0 }"
>
{{ formatNumber(slotProps.data.sold) }}
</span>
</template>
</Column>
<Column field="explicatia" header="Explicație" class="col-explicatie" />
<Column
field="explicatia"
header="Explicație"
class="col-explicatie"
/>
</DataTable>
</template>
</Card>
@@ -286,17 +332,26 @@ const formatDate = (dateString) => {
// Computed total sold (incasari - plati)
const totalSold = computed(() => {
return (treasuryStore.totals.total_incasari || 0) - (treasuryStore.totals.total_plati || 0);
return (
(treasuryStore.totals.total_incasari || 0) -
(treasuryStore.totals.total_plati || 0)
);
});
// Check if current filter is a VALUTA type (to show Valuta column)
const isValutaType = computed(() => {
return filters.value.registerType === "CASA_VALUTA" || filters.value.registerType === "BANCA_VALUTA";
return (
filters.value.registerType === "CASA_VALUTA" ||
filters.value.registerType === "BANCA_VALUTA"
);
});
// Check if current filter is BANCA type (for dynamic column header)
const isBancaType = computed(() => {
return filters.value.registerType === "BANCA_LEI" || filters.value.registerType === "BANCA_VALUTA";
return (
filters.value.registerType === "BANCA_LEI" ||
filters.value.registerType === "BANCA_VALUTA"
);
});
// Dynamic column header for Casa/Banca
@@ -345,10 +400,10 @@ const removeDiacritics = (text) => {
// Get register type label for PDF (no diacritics)
const getRegisterTypeLabel = (type) => {
const labels = {
"CASA_LEI": "Casa LEI",
"CASA_VALUTA": "Casa Valuta",
"BANCA_LEI": "Banca LEI",
"BANCA_VALUTA": "Banca Valuta",
CASA_LEI: "Casa LEI",
CASA_VALUTA: "Casa Valuta",
BANCA_LEI: "Banca LEI",
BANCA_VALUTA: "Banca Valuta",
};
return labels[type] || type;
};
@@ -356,10 +411,10 @@ const getRegisterTypeLabel = (type) => {
// Get PDF title based on register type
const getPdfTitle = (type) => {
const titles = {
"CASA_LEI": "Registrul de Casa LEI",
"CASA_VALUTA": "Registrul de Casa Valuta",
"BANCA_LEI": "Registrul de Banca LEI",
"BANCA_VALUTA": "Registrul de Banca Valuta",
CASA_LEI: "Registrul de Casa LEI",
CASA_VALUTA: "Registrul de Casa Valuta",
BANCA_LEI: "Registrul de Banca LEI",
BANCA_VALUTA: "Registrul de Banca Valuta",
};
return titles[type] || "Registrul de Casa si Banca";
};
@@ -393,7 +448,7 @@ watch(
// Reset bank account selection when register type changes
filters.value.bankAccount = null;
await loadBankAccounts();
}
},
);
const getRowClass = (data) => {
@@ -481,13 +536,19 @@ const fetchAllData = async () => {
// Add optional filters (use LOCAL date, not UTC)
if (filters.value.dateFrom) {
const year = filters.value.dateFrom.getFullYear();
const month = String(filters.value.dateFrom.getMonth() + 1).padStart(2, "0");
const month = String(filters.value.dateFrom.getMonth() + 1).padStart(
2,
"0",
);
const day = String(filters.value.dateFrom.getDate()).padStart(2, "0");
params.date_from = `${year}-${month}-${day}`;
}
if (filters.value.dateTo) {
const year = filters.value.dateTo.getFullYear();
const month = String(filters.value.dateTo.getMonth() + 1).padStart(2, "0");
const month = String(filters.value.dateTo.getMonth() + 1).padStart(
2,
"0",
);
const day = String(filters.value.dateTo.getDate()).padStart(2, "0");
params.date_to = `${year}-${month}-${day}`;
}
@@ -499,7 +560,9 @@ const fetchAllData = async () => {
}
const apiService = (await import("../services/api")).apiService;
const response = await apiService.get("/treasury/bank-cash-register", { params });
const response = await apiService.get("/treasury/bank-cash-register", {
params,
});
return response.data.registers || [];
} catch (error) {
@@ -543,7 +606,7 @@ const exportExcel = async () => {
// Column order: Data, Nr., Casă/Bancă, Partener, [Valuta], Încasări, Plăți, Sold Cumulat, Explicație
const exportData = allData.map((row) => {
const baseData = {
"Data": row.dataact ? formatDate(row.dataact) : "",
Data: row.dataact ? formatDate(row.dataact) : "",
"Nr.": row.nract || "",
};
@@ -667,7 +730,10 @@ const loadData = async () => {
// Format dates properly using local time
if (filters.value.dateFrom) {
const year = filters.value.dateFrom.getFullYear();
const month = String(filters.value.dateFrom.getMonth() + 1).padStart(2, "0");
const month = String(filters.value.dateFrom.getMonth() + 1).padStart(
2,
"0",
);
const day = String(filters.value.dateFrom.getDate()).padStart(2, "0");
filterParams.date_from = `${year}-${month}-${day}`;
}
@@ -710,215 +776,47 @@ watch(
</script>
<style scoped>
/* Page Layout */
/* ===== Page-Specific Styles Only ===== */
/* Uses shared CSS: dashboard.css (.page-header, .page-title, .page-subtitle) */
/* Uses shared CSS: forms.css (.form-actions) */
/* Uses shared CSS: tables.css (.table-empty, .loading-state, .negative) */
/* Uses shared CSS: stats.css (.summary-stats-inline) */
/* Uses shared CSS: primevue-overrides.css (DataTable striped rows, hover, compact) */
/* Page Container */
.register-view {
max-width: 1400px;
margin: 0 auto;
padding: 2rem;
padding: var(--space-xl);
}
.page-header {
margin-bottom: 2rem;
}
.page-title {
font-size: 2.5rem;
font-weight: 700;
color: var(--text-color);
margin: 0 0 0.5rem 0;
display: flex;
align-items: center;
gap: 0.75rem;
}
.page-subtitle {
font-size: 1.1rem;
color: var(--text-color-secondary);
margin: 0;
}
/* Cards */
/* Card Spacing */
.company-selection-card,
.filters-card {
margin-bottom: 2rem;
.filters-card,
.data-card {
margin-bottom: var(--space-xl);
}
/* Filters Actions Row */
.filters-actions {
display: flex;
gap: 1rem;
justify-content: flex-end;
padding-top: 1rem;
border-top: 1px solid var(--surface-border);
}
/* Summary Stats - Compact, right aligned */
.summary-stats-compact {
display: flex;
gap: 2rem;
justify-content: flex-end;
margin-bottom: 1rem;
padding: 0.75rem 1rem;
background-color: var(--surface-ground);
border-radius: var(--border-radius);
border: 1px solid var(--surface-border);
}
.stat-item {
display: flex;
align-items: center;
gap: 0.5rem;
}
.stat-item .stat-label {
font-size: 0.875rem;
color: var(--text-color-secondary);
}
.stat-item .stat-value {
font-size: 0.9rem;
font-weight: 600;
font-variant-numeric: tabular-nums;
}
.stat-item .stat-value.incasari {
color: var(--green-600);
}
.stat-item .stat-value.plati {
color: var(--red-600);
}
/* Empty and Loading States */
.no-data,
.loading-table {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 3rem;
color: var(--text-color-secondary);
}
.no-data i,
.loading-table i {
font-size: 2rem;
margin-bottom: 0.5rem;
}
/* Table Striped Rows */
.data-card :deep(.p-datatable .p-datatable-tbody > tr) {
transition: background-color 0.2s ease;
}
.data-card :deep(.p-datatable .p-datatable-tbody > tr:nth-child(odd)) {
background-color: #ffffff;
}
.data-card :deep(.p-datatable .p-datatable-tbody > tr:nth-child(even)) {
background-color: #f8f9fa;
}
.data-card :deep(.p-datatable .p-datatable-tbody > tr:hover) {
background-color: #e3f2fd !important;
cursor: pointer;
}
/* Compact table styling */
.data-card :deep(.p-datatable) {
font-size: 0.875rem;
}
.data-card :deep(.p-datatable .p-datatable-thead > tr > th) {
padding: 0.5rem 0.75rem;
font-weight: 600;
background-color: var(--surface-100);
white-space: nowrap;
}
.data-card :deep(.p-datatable .p-datatable-tbody > tr > td) {
padding: 0.4rem 0.75rem;
}
/* Column widths */
.data-card :deep(.col-data) {
width: 90px;
min-width: 90px;
}
.data-card :deep(.col-nr) {
width: 60px;
min-width: 60px;
text-align: center;
}
.data-card :deep(.col-partener) {
min-width: 180px;
width: 22%;
}
.data-card :deep(.col-cont) {
min-width: 160px;
width: 18%;
}
.data-card :deep(.col-valuta) {
width: 70px;
min-width: 70px;
text-align: center;
}
.data-card :deep(.col-numeric) {
width: 110px;
min-width: 100px;
text-align: right;
}
.data-card :deep(.col-numeric .p-column-header-content) {
justify-content: flex-end;
}
.data-card :deep(.col-sold) {
width: 120px;
min-width: 110px;
}
.data-card :deep(.col-explicatie) {
min-width: 120px;
}
/* Numeric values styling */
/* Numeric Values - Page-specific formatting */
.numeric-value {
display: block;
text-align: right;
font-variant-numeric: tabular-nums;
font-family: "Roboto Mono", "Consolas", monospace;
font-family: var(--font-mono, "Roboto Mono", "Consolas", monospace);
}
.numeric-value.zero {
color: var(--text-color-secondary);
color: var(--color-text-muted);
}
.numeric-value.negative {
color: var(--red-600);
color: var(--color-error);
}
/* Responsive */
@media (max-width: 768px) {
.register-view {
padding: 1rem;
}
.page-title {
font-size: 2rem;
}
.summary-stats {
grid-template-columns: 1fr;
}
.filters-actions {
flex-direction: column;
padding: var(--space-md);
}
}
</style>

View File

@@ -65,7 +65,7 @@
</div>
</div>
<div class="filters-actions">
<div class="form-actions">
<Button
icon="pi pi-filter-slash"
label="Resetează Filtre"
@@ -116,14 +116,16 @@
@sort="onSort"
>
<template #empty>
<div class="no-data">
<i class="pi pi-info-circle"></i>
<p>Nu au fost găsite date pentru perioada selectată</p>
<div class="table-empty">
<i class="pi pi-info-circle table-empty-icon"></i>
<p class="table-empty-message">
Nu au fost găsite date pentru perioada selectată
</p>
</div>
</template>
<template #loading>
<div class="loading-table">
<div class="loading-state">
<ProgressSpinner />
<p>Se încarcă balanța de verificare...</p>
</div>
@@ -615,107 +617,44 @@ watch(
</script>
<style scoped>
/* ===== Page-Specific Styles Only ===== */
/* Uses shared CSS: dashboard.css (.page-header, .page-title, .page-subtitle) */
/* Uses shared CSS: forms.css (.form-actions) */
/* Uses shared CSS: tables.css (.table-empty, .loading-state) */
/* Uses shared CSS: primevue-overrides.css (DataTable striped rows, hover) */
/* Page Container */
.trial-balance {
max-width: 1400px;
margin: 0 auto;
padding: 2rem;
}
.page-header {
margin-bottom: 2rem;
}
.page-title {
font-size: 2.5rem;
font-weight: 700;
color: var(--text-color);
margin: 0 0 0.5rem 0;
display: flex;
align-items: center;
gap: 0.75rem;
}
.page-subtitle {
font-size: 1.1rem;
color: var(--text-color-secondary);
margin: 0;
padding: var(--space-xl);
}
/* Card Spacing */
.company-selection-card,
.filters-card {
margin-bottom: 2rem;
.filters-card,
.table-card {
margin-bottom: var(--space-xl);
}
/* Search field takes 2 columns in form grid */
.search-col {
grid-column: span 2;
}
.filters-actions {
display: flex;
gap: 1rem;
justify-content: flex-end;
padding-top: 1rem;
border-top: 1px solid var(--surface-border);
}
.table-card {
margin-bottom: 2rem;
}
.no-data,
.loading-table {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 3rem;
color: var(--text-color-secondary);
}
.no-data i,
.loading-table i {
font-size: 2rem;
margin-bottom: 0.5rem;
}
/* Text alignment utility - page specific */
.text-right {
text-align: right;
}
/* Enhanced striped rows with better contrast */
.table-card :deep(.p-datatable .p-datatable-tbody > tr) {
transition: background-color 0.2s ease;
}
.table-card :deep(.p-datatable .p-datatable-tbody > tr:nth-child(odd)) {
background-color: #ffffff;
}
.table-card :deep(.p-datatable .p-datatable-tbody > tr:nth-child(even)) {
background-color: #f8f9fa;
}
.table-card :deep(.p-datatable .p-datatable-tbody > tr:hover) {
background-color: #e3f2fd !important;
cursor: pointer;
}
/* Responsive design */
/* Responsive */
@media (max-width: 768px) {
.trial-balance {
padding: 1rem;
}
.page-title {
font-size: 2rem;
padding: var(--space-md);
}
.search-col {
grid-column: span 1;
}
.filters-actions {
flex-direction: column;
}
}
</style>