feat: Add mobile-optimized card layout and compact UI for all report views
- Add mobile card layout for Invoices, Treasury, and Trial Balance views - Implement two-row mobile toolbar with icon-only action buttons - Add uniform totals grid across all views with compact number formatting - Move profile menu to hamburger menu on mobile devices - Fix company selector and period selector truncation on mobile - Add mobile-specific CSS with responsive breakpoints 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -86,6 +86,9 @@
|
|||||||
transition: transform var(--transition-normal);
|
transition: transform var(--transition-normal);
|
||||||
z-index: var(--z-modal);
|
z-index: var(--z-modal);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
/* Flex container for profile section at bottom */
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.slide-menu.open {
|
.slide-menu.open {
|
||||||
|
|||||||
@@ -693,3 +693,401 @@
|
|||||||
min-height: 44px; /* Touch-friendly height */
|
min-height: 44px; /* Touch-friendly height */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
Mobile Compact Toolbar
|
||||||
|
Ultra-compact header for mobile views
|
||||||
|
- Filter toggle (funnel icon)
|
||||||
|
- Actions dropdown menu
|
||||||
|
- Single compact total display
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
/* Mobile Toolbar - compact single line (~50px) */
|
||||||
|
.mobile-toolbar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-sm, 0.5rem);
|
||||||
|
padding: var(--space-sm, 0.5rem) var(--space-md, 1rem);
|
||||||
|
background: var(--surface-card, #ffffff);
|
||||||
|
border: 1px solid var(--surface-border, #e2e8f0);
|
||||||
|
border-radius: var(--border-radius, 6px);
|
||||||
|
margin-bottom: var(--space-md, 1rem);
|
||||||
|
min-height: 50px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Filter toggle button - colored when filters are active */
|
||||||
|
.mobile-toolbar .filter-active {
|
||||||
|
color: var(--primary-color, #2563eb) !important;
|
||||||
|
background: rgba(37, 99, 235, 0.1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-toolbar .filter-active:hover {
|
||||||
|
background: rgba(37, 99, 235, 0.2) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Actions button - compact */
|
||||||
|
.mobile-toolbar .p-button-outlined {
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Compact total display - pushed to right */
|
||||||
|
.mobile-toolbar .mobile-total {
|
||||||
|
margin-left: auto;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-xs, 0.25rem);
|
||||||
|
font-size: var(--text-sm, 0.875rem);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
max-width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-toolbar .total-label {
|
||||||
|
color: var(--text-color-secondary, #64748b);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-toolbar .total-value {
|
||||||
|
font-weight: var(--font-semibold, 600);
|
||||||
|
color: var(--text-color, #1e293b);
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Color classes for positive/negative values */
|
||||||
|
.mobile-toolbar .total-value.incasari {
|
||||||
|
color: var(--green-600, #16a34a);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-toolbar .total-value.plati {
|
||||||
|
color: var(--red-600, #dc2626);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile-only visibility - show toolbar only on mobile */
|
||||||
|
@media (min-width: 769px) {
|
||||||
|
.mobile-toolbar {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Extra compact on very small screens */
|
||||||
|
@media (max-width: 400px) {
|
||||||
|
.mobile-toolbar {
|
||||||
|
padding: var(--space-xs, 0.25rem) var(--space-sm, 0.5rem);
|
||||||
|
gap: var(--space-xs, 0.25rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-toolbar .mobile-total {
|
||||||
|
font-size: var(--text-xs, 0.75rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-toolbar .p-button-outlined {
|
||||||
|
padding: 0.375rem 0.5rem;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide label on very small screens, show only icon */
|
||||||
|
.mobile-toolbar .p-button-outlined .p-button-label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-toolbar .p-button-outlined .p-button-icon {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Filters card - more compact when visible on mobile */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.filters-card {
|
||||||
|
margin-bottom: var(--space-sm, 0.5rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters-card .p-card-body {
|
||||||
|
padding: var(--space-sm, 0.5rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters-card .form-row {
|
||||||
|
gap: var(--space-sm, 0.5rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters-card .form-group {
|
||||||
|
margin-bottom: var(--space-xs, 0.25rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters-card .form-label {
|
||||||
|
font-size: var(--text-sm, 0.875rem);
|
||||||
|
margin-bottom: var(--space-xs, 0.25rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
Mobile Data Cards
|
||||||
|
Compact card layout for table data on mobile
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
.mobile-card-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-data-card {
|
||||||
|
background: var(--surface-card, #ffffff);
|
||||||
|
border: 1px solid var(--surface-border, #e2e8f0);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-data-card .card-header {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
color: var(--text-color, #1e293b);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-data-card .card-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--text-color-secondary, #64748b);
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-data-card .card-meta {
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-data-card .card-amount {
|
||||||
|
font-weight: 600;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
font-size: 0.9375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-data-card .card-amount.positive {
|
||||||
|
color: var(--green-600, #16a34a);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-data-card .card-amount.negative {
|
||||||
|
color: var(--red-600, #dc2626);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile empty state */
|
||||||
|
.mobile-empty {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 2rem;
|
||||||
|
color: var(--text-color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-empty i {
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
Mobile Responsive Totals
|
||||||
|
Unified grid layout for totals on mobile
|
||||||
|
Supports 1, 2, or 4 totals uniformly
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
.mobile-totals-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 0.375rem 1rem;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
background: var(--surface-ground, #f8fafc);
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Single total - center it */
|
||||||
|
.mobile-totals-grid.single-total {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
justify-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Two totals - side by side */
|
||||||
|
.mobile-totals-grid.two-totals {
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-totals-grid .total-item {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-totals-grid .total-label {
|
||||||
|
color: var(--text-color-secondary, #64748b);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-totals-grid .total-value {
|
||||||
|
font-weight: 600;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-totals-grid .total-value.incasari,
|
||||||
|
.mobile-totals-grid .total-value.positive {
|
||||||
|
color: var(--green-600, #16a34a);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-totals-grid .total-value.plati,
|
||||||
|
.mobile-totals-grid .total-value.negative {
|
||||||
|
color: var(--red-600, #dc2626);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Backward compatibility - stack totals (deprecated, use grid) */
|
||||||
|
.mobile-totals-stack {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.125rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-totals-stack .total-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-totals-stack .total-label {
|
||||||
|
color: var(--text-color-secondary, #64748b);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-totals-stack .total-value {
|
||||||
|
font-weight: 600;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
Mobile Toolbar v2 - Two-Row Layout
|
||||||
|
Row 1: Icon-only action buttons
|
||||||
|
Row 2: Totals display
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
.mobile-toolbar-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-sm, 0.5rem);
|
||||||
|
padding: var(--space-sm, 0.5rem) var(--space-md, 1rem);
|
||||||
|
background: var(--surface-card, #ffffff);
|
||||||
|
border: 1px solid var(--surface-border, #e2e8f0);
|
||||||
|
border-radius: var(--border-radius, 6px);
|
||||||
|
margin-bottom: var(--space-md, 1rem);
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Row 1: Icon-only action buttons */
|
||||||
|
.mobile-toolbar-buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-xs, 0.25rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Icon-only buttons - no labels */
|
||||||
|
.mobile-toolbar-buttons .p-button {
|
||||||
|
padding: var(--space-sm, 0.5rem);
|
||||||
|
min-width: 44px;
|
||||||
|
min-height: 44px;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-toolbar-buttons .p-button .p-button-label {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-toolbar-buttons .p-button .p-button-icon {
|
||||||
|
margin-right: 0 !important;
|
||||||
|
font-size: 1.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Filter active state */
|
||||||
|
.mobile-toolbar-buttons .filter-active {
|
||||||
|
color: var(--primary-color, #2563eb) !important;
|
||||||
|
background: rgba(37, 99, 235, 0.1) !important;
|
||||||
|
border-color: var(--primary-color, #2563eb) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-toolbar-buttons .filter-active:hover {
|
||||||
|
background: rgba(37, 99, 235, 0.2) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Row 2: Totals display */
|
||||||
|
.mobile-toolbar-totals {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: var(--space-xs, 0.25rem);
|
||||||
|
border-top: 1px solid var(--surface-border, #e2e8f0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Center the totals grid/stack in row 2 */
|
||||||
|
.mobile-toolbar-totals .mobile-totals-grid,
|
||||||
|
.mobile-toolbar-totals .mobile-totals-stack,
|
||||||
|
.mobile-toolbar-totals .mobile-total {
|
||||||
|
margin-left: 0;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide on desktop */
|
||||||
|
@media (min-width: 769px) {
|
||||||
|
.mobile-toolbar-container {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Extra compact on very small screens */
|
||||||
|
@media (max-width: 400px) {
|
||||||
|
.mobile-toolbar-container {
|
||||||
|
padding: var(--space-xs, 0.25rem) var(--space-sm, 0.5rem);
|
||||||
|
gap: var(--space-xs, 0.25rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-toolbar-buttons .p-button {
|
||||||
|
padding: var(--space-xs, 0.25rem);
|
||||||
|
min-width: 40px;
|
||||||
|
min-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-toolbar-buttons .p-button .p-button-icon {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
Hamburger Menu Profile Section
|
||||||
|
Profile options at bottom of slide menu
|
||||||
|
============================================ */
|
||||||
|
|
||||||
|
.menu-profile {
|
||||||
|
margin-top: auto;
|
||||||
|
border-top: 1px solid var(--color-border, #e2e8f0);
|
||||||
|
padding-top: var(--space-md, 1rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-profile .profile-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-sm, 0.5rem);
|
||||||
|
padding: var(--space-sm, 0.5rem) var(--space-md, 1rem);
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text, #1e293b);
|
||||||
|
font-size: var(--text-sm, 0.875rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-profile .profile-info .pi-user {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
color: var(--color-primary, #2563eb);
|
||||||
|
}
|
||||||
|
|||||||
@@ -497,18 +497,55 @@ export default {
|
|||||||
/* Mobile adjustments */
|
/* Mobile adjustments */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.company-selector-mini {
|
.company-selector-mini {
|
||||||
max-width: none;
|
max-width: 200px;
|
||||||
width: 100%;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.company-trigger {
|
.company-trigger {
|
||||||
min-width: auto;
|
min-width: auto;
|
||||||
|
max-width: 200px;
|
||||||
|
padding: var(--space-xs) var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.company-info {
|
||||||
|
max-width: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.company-name {
|
||||||
|
font-size: var(--text-xs);
|
||||||
|
max-width: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.company-code {
|
||||||
|
font-size: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.company-dropdown-panel {
|
.company-dropdown-panel {
|
||||||
left: -16px;
|
position: fixed;
|
||||||
right: -16px;
|
left: 8px;
|
||||||
width: calc(100% + 32px);
|
right: 8px;
|
||||||
|
top: 60px;
|
||||||
|
width: auto;
|
||||||
|
max-height: 70vh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Extra small screens */
|
||||||
|
@media (max-width: 400px) {
|
||||||
|
.company-selector-mini {
|
||||||
|
max-width: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.company-trigger {
|
||||||
|
max-width: 160px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.company-info {
|
||||||
|
max-width: 110px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.company-name {
|
||||||
|
max-width: 110px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -349,18 +349,36 @@ export default {
|
|||||||
/* Mobile adjustments */
|
/* Mobile adjustments */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.period-selector-mini {
|
.period-selector-mini {
|
||||||
max-width: none;
|
max-width: 140px;
|
||||||
width: 100%;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.period-trigger {
|
.period-trigger {
|
||||||
min-width: auto;
|
min-width: auto;
|
||||||
|
padding: var(--space-xs) var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.period-info {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.period-label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.period-name {
|
||||||
|
font-size: var(--text-xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
.period-dropdown-panel {
|
.period-dropdown-panel {
|
||||||
left: -16px;
|
position: fixed;
|
||||||
right: -16px;
|
left: 8px;
|
||||||
width: calc(100% + 32px);
|
right: 8px;
|
||||||
|
top: 60px;
|
||||||
|
width: auto;
|
||||||
|
max-height: 70vh;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
v-model="selectedCompany"
|
v-model="selectedCompany"
|
||||||
@company-changed="onCompanyChanged"
|
@company-changed="onCompanyChanged"
|
||||||
/>
|
/>
|
||||||
<div class="user-menu-container">
|
<div class="user-menu-container mobile-hide">
|
||||||
<div class="header-user" @click="toggleUserMenu">
|
<div class="header-user" @click="toggleUserMenu">
|
||||||
<i class="pi pi-user"></i>
|
<i class="pi pi-user"></i>
|
||||||
<span class="desktop-only">{{
|
<span class="desktop-only">{{
|
||||||
@@ -311,5 +311,10 @@ export default {
|
|||||||
.user-dropdown-item {
|
.user-dropdown-item {
|
||||||
padding: var(--space-sm);
|
padding: var(--space-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Hide profile menu on mobile - use hamburger menu instead */
|
||||||
|
.mobile-hide {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -75,6 +75,16 @@
|
|||||||
<span>Statistici cache</span>
|
<span>Statistici cache</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Profile Section (at bottom) -->
|
||||||
|
<div class="menu-section menu-profile">
|
||||||
|
<div class="profile-info">
|
||||||
|
<i class="pi pi-user"></i>
|
||||||
|
<span>{{ currentUser?.username || 'Utilizator' }}</span>
|
||||||
|
</div>
|
||||||
|
<ul class="menu-list">
|
||||||
<li class="menu-item">
|
<li class="menu-item">
|
||||||
<router-link
|
<router-link
|
||||||
to="/telegram"
|
to="/telegram"
|
||||||
@@ -86,6 +96,12 @@
|
|||||||
<span>Telegram Bot</span>
|
<span>Telegram Bot</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="menu-item">
|
||||||
|
<a href="#" class="menu-link" @click.prevent="handleLogout">
|
||||||
|
<i class="menu-icon pi pi-sign-out"></i>
|
||||||
|
<span>Deconectare</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
@@ -93,6 +109,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { computed } from "vue";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import { useAuthStore } from "../../stores/auth";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "HamburgerMenu",
|
name: "HamburgerMenu",
|
||||||
props: {
|
props: {
|
||||||
@@ -103,12 +123,29 @@ export default {
|
|||||||
},
|
},
|
||||||
emits: ["close"],
|
emits: ["close"],
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
|
const router = useRouter();
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
|
const currentUser = computed(() => authStore.currentUser);
|
||||||
|
|
||||||
const closeMenu = () => {
|
const closeMenu = () => {
|
||||||
emit("close");
|
emit("close");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleLogout = async () => {
|
||||||
|
try {
|
||||||
|
authStore.logout();
|
||||||
|
closeMenu();
|
||||||
|
await router.push("/login");
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Logout error:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
currentUser,
|
||||||
closeMenu,
|
closeMenu,
|
||||||
|
handleLogout,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,9 +7,6 @@
|
|||||||
<i class="pi pi-wallet"></i>
|
<i class="pi pi-wallet"></i>
|
||||||
Registru Casă / Bancă
|
Registru Casă / Bancă
|
||||||
</h1>
|
</h1>
|
||||||
<p class="page-subtitle">
|
|
||||||
Selectați tipul de registru pentru a vizualiza mișcările
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Company Selection (when no company selected) -->
|
<!-- Company Selection (when no company selected) -->
|
||||||
@@ -33,8 +30,71 @@
|
|||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
<!-- Mobile: Two-row toolbar -->
|
||||||
|
<div v-if="isMobile && companyStore.selectedCompany" class="mobile-toolbar-container">
|
||||||
|
<!-- Row 1: Icon-only action buttons -->
|
||||||
|
<div class="mobile-toolbar-buttons">
|
||||||
|
<Button
|
||||||
|
icon="pi pi-filter"
|
||||||
|
:class="{ 'filter-active': hasActiveFilters }"
|
||||||
|
class="p-button-text"
|
||||||
|
@click="showFilters = !showFilters"
|
||||||
|
v-tooltip.bottom="'Filtre'"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
icon="pi pi-filter-slash"
|
||||||
|
class="p-button-text"
|
||||||
|
@click="resetFilters"
|
||||||
|
v-tooltip.bottom="'Resetează'"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
icon="pi pi-file-excel"
|
||||||
|
class="p-button-text p-button-success"
|
||||||
|
@click="exportExcel"
|
||||||
|
:disabled="!hasData"
|
||||||
|
v-tooltip.bottom="'Excel'"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
icon="pi pi-file-pdf"
|
||||||
|
class="p-button-text p-button-danger"
|
||||||
|
@click="exportPDF"
|
||||||
|
:disabled="!hasData"
|
||||||
|
v-tooltip.bottom="'PDF'"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
icon="pi pi-refresh"
|
||||||
|
class="p-button-text"
|
||||||
|
:loading="treasuryStore.isLoading"
|
||||||
|
@click="refreshData"
|
||||||
|
v-tooltip.bottom="'Actualizează'"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Row 2: Totals grid -->
|
||||||
|
<div class="mobile-toolbar-totals">
|
||||||
|
<div class="mobile-totals-grid">
|
||||||
|
<div class="total-item">
|
||||||
|
<span class="total-label">Sold Prec:</span>
|
||||||
|
<span class="total-value">{{ formatCompact(treasuryStore.totals.sold_precedent_all) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="total-item">
|
||||||
|
<span class="total-label">Încasări:</span>
|
||||||
|
<span class="total-value incasari">{{ formatCompact(treasuryStore.totals.total_incasari_all) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="total-item">
|
||||||
|
<span class="total-label">Plăți:</span>
|
||||||
|
<span class="total-value plati">{{ formatCompact(treasuryStore.totals.total_plati_all) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="total-item">
|
||||||
|
<span class="total-label">Sold Final:</span>
|
||||||
|
<span class="total-value">{{ formatCompact(treasuryStore.totals.sold_final_all) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Filters -->
|
<!-- Filters -->
|
||||||
<Card v-if="companyStore.selectedCompany" class="filters-card">
|
<Card v-if="companyStore.selectedCompany && (!isMobile || showFilters)" class="filters-card">
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="form">
|
<div class="form">
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
@@ -81,8 +141,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Separate action buttons row -->
|
<!-- Desktop: Action buttons -->
|
||||||
<div class="form-actions">
|
<div v-if="!isMobile" class="form-actions">
|
||||||
<Button
|
<Button
|
||||||
icon="pi pi-filter-slash"
|
icon="pi pi-filter-slash"
|
||||||
label="Resetează Filtre"
|
label="Resetează Filtre"
|
||||||
@@ -114,9 +174,9 @@
|
|||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<!-- Summary Stats - Compact, right aligned -->
|
<!-- Summary Stats - Compact, right aligned (hidden on mobile - only Sold Final in toolbar) -->
|
||||||
<!-- Folosește totaluri din TOATE înregistrările (backend) nu doar pagina curentă -->
|
<!-- Folosește totaluri din TOATE înregistrările (backend) nu doar pagina curentă -->
|
||||||
<div v-if="companyStore.selectedCompany" class="summary-stats-inline">
|
<div v-if="!isMobile && companyStore.selectedCompany" class="summary-stats-inline">
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
<span class="stat-label">Sold Precedent:</span>
|
<span class="stat-label">Sold Precedent:</span>
|
||||||
<span
|
<span
|
||||||
@@ -150,7 +210,35 @@
|
|||||||
<!-- Data Table -->
|
<!-- Data Table -->
|
||||||
<Card v-if="companyStore.selectedCompany" class="data-card">
|
<Card v-if="companyStore.selectedCompany" class="data-card">
|
||||||
<template #content>
|
<template #content>
|
||||||
|
<!-- Mobile: Card Layout -->
|
||||||
|
<div v-if="isMobile" class="mobile-card-list">
|
||||||
|
<div
|
||||||
|
v-for="reg in treasuryStore.registers"
|
||||||
|
:key="`${reg.dataact}-${reg.nract}`"
|
||||||
|
class="mobile-data-card"
|
||||||
|
>
|
||||||
|
<div class="card-header">{{ reg.nume || 'Fără partener' }}</div>
|
||||||
|
<div class="card-row">
|
||||||
|
<span class="card-meta">{{ formatDateShort(reg.dataact) }} · {{ reg.nume_cont_bancar }}</span>
|
||||||
|
<span
|
||||||
|
class="card-amount"
|
||||||
|
:class="reg.incasari > 0 ? 'positive' : (reg.plati > 0 ? 'negative' : '')"
|
||||||
|
>
|
||||||
|
<template v-if="reg.incasari > 0">+{{ formatNumber(reg.incasari) }}</template>
|
||||||
|
<template v-else-if="reg.plati > 0">-{{ formatNumber(reg.plati) }}</template>
|
||||||
|
<template v-else>{{ formatNumber(0) }}</template>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="treasuryStore.registers.length === 0" class="mobile-empty">
|
||||||
|
<i class="pi pi-info-circle"></i>
|
||||||
|
<p>Nu au fost găsite înregistrări</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Desktop: DataTable -->
|
||||||
<DataTable
|
<DataTable
|
||||||
|
v-if="!isMobile"
|
||||||
:value="treasuryStore.registers"
|
:value="treasuryStore.registers"
|
||||||
:loading="treasuryStore.isLoading"
|
:loading="treasuryStore.isLoading"
|
||||||
:paginator="true"
|
:paginator="true"
|
||||||
@@ -260,7 +348,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted, watch } from "vue";
|
import { ref, computed, onMounted, onUnmounted, watch } from "vue";
|
||||||
import { useToast } from "primevue/usetoast";
|
import { useToast } from "primevue/usetoast";
|
||||||
import { useTreasuryStore } from "../stores/treasury";
|
import { useTreasuryStore } from "../stores/treasury";
|
||||||
import { useCompanyStore } from "../stores/companies";
|
import { useCompanyStore } from "../stores/companies";
|
||||||
@@ -276,6 +364,19 @@ const periodStore = useAccountingPeriodStore();
|
|||||||
// State for company selection
|
// State for company selection
|
||||||
const selectedCompanyId = ref(companyStore.selectedCompany?.id_firma || null);
|
const selectedCompanyId = ref(companyStore.selectedCompany?.id_firma || null);
|
||||||
|
|
||||||
|
// Mobile state
|
||||||
|
const isMobile = ref(window.innerWidth < 768);
|
||||||
|
const showFilters = ref(false);
|
||||||
|
const actionsMenu = ref(null);
|
||||||
|
|
||||||
|
// Handle window resize
|
||||||
|
const handleResize = () => {
|
||||||
|
isMobile.value = window.innerWidth < 768;
|
||||||
|
if (!isMobile.value) {
|
||||||
|
showFilters.value = false; // Reset when switching to desktop
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Register type options for dropdown - doar cele 4 tipuri, fără "Toate"
|
// Register type options for dropdown - doar cele 4 tipuri, fără "Toate"
|
||||||
const registerTypeOptions = [
|
const registerTypeOptions = [
|
||||||
{ label: "Casă LEI", value: "CASA_LEI" },
|
{ label: "Casă LEI", value: "CASA_LEI" },
|
||||||
@@ -319,6 +420,27 @@ const formatDate = (dateString) => {
|
|||||||
return format(new Date(dateString), "dd.MM.yyyy");
|
return format(new Date(dateString), "dd.MM.yyyy");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Short date format for mobile cards (DD/MM)
|
||||||
|
const formatDateShort = (dateString) => {
|
||||||
|
if (!dateString) return "";
|
||||||
|
const date = new Date(dateString);
|
||||||
|
return `${String(date.getDate()).padStart(2, "0")}/${String(date.getMonth() + 1).padStart(2, "0")}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Compact number format (no decimals for large numbers)
|
||||||
|
const formatCompact = (amount) => {
|
||||||
|
if (!amount) return "0";
|
||||||
|
if (Math.abs(amount) >= 10000) {
|
||||||
|
return new Intl.NumberFormat("ro-RO", {
|
||||||
|
maximumFractionDigits: 0,
|
||||||
|
}).format(amount);
|
||||||
|
}
|
||||||
|
return new Intl.NumberFormat("ro-RO", {
|
||||||
|
minimumFractionDigits: 2,
|
||||||
|
maximumFractionDigits: 2,
|
||||||
|
}).format(amount);
|
||||||
|
};
|
||||||
|
|
||||||
// Truncate text to maxLength characters
|
// Truncate text to maxLength characters
|
||||||
const truncateText = (text, maxLength = 100) => {
|
const truncateText = (text, maxLength = 100) => {
|
||||||
if (!text) return "";
|
if (!text) return "";
|
||||||
@@ -447,6 +569,42 @@ const resetFilters = async () => {
|
|||||||
// Computed
|
// Computed
|
||||||
const hasData = computed(() => treasuryStore.registers.length > 0);
|
const hasData = computed(() => treasuryStore.registers.length > 0);
|
||||||
|
|
||||||
|
// Mobile: Check if any filter is active (non-default value)
|
||||||
|
const hasActiveFilters = computed(() => {
|
||||||
|
return (
|
||||||
|
filters.value.registerType !== "BANCA_LEI" ||
|
||||||
|
filters.value.partnerName !== "" ||
|
||||||
|
filters.value.bankAccount !== null
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mobile: Actions menu items
|
||||||
|
const actionMenuItems = computed(() => [
|
||||||
|
{
|
||||||
|
label: "Resetează Filtre",
|
||||||
|
icon: "pi pi-filter-slash",
|
||||||
|
command: resetFilters,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Export Excel",
|
||||||
|
icon: "pi pi-file-excel",
|
||||||
|
command: exportExcel,
|
||||||
|
disabled: !hasData.value,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Export PDF",
|
||||||
|
icon: "pi pi-file-pdf",
|
||||||
|
command: exportPDF,
|
||||||
|
disabled: !hasData.value,
|
||||||
|
},
|
||||||
|
{ separator: true },
|
||||||
|
{
|
||||||
|
label: "Actualizează",
|
||||||
|
icon: "pi pi-refresh",
|
||||||
|
command: refreshData,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
// Handle company change from dropdown
|
// Handle company change from dropdown
|
||||||
const handleCompanyChange = async () => {
|
const handleCompanyChange = async () => {
|
||||||
if (!selectedCompanyId.value) return;
|
if (!selectedCompanyId.value) return;
|
||||||
@@ -696,6 +854,9 @@ const loadData = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
// Add resize listener for mobile detection
|
||||||
|
window.addEventListener("resize", handleResize);
|
||||||
|
|
||||||
// Load companies if not loaded
|
// Load companies if not loaded
|
||||||
if (!companyStore.hasCompanies) {
|
if (!companyStore.hasCompanies) {
|
||||||
await companyStore.loadCompanies();
|
await companyStore.loadCompanies();
|
||||||
@@ -708,6 +869,10 @@ onMounted(async () => {
|
|||||||
// Don't load data here - let period watch handle it with immediate: true
|
// Don't load data here - let period watch handle it with immediate: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener("resize", handleResize);
|
||||||
|
});
|
||||||
|
|
||||||
// Watch for company changes
|
// Watch for company changes
|
||||||
watch(
|
watch(
|
||||||
() => companyStore.selectedCompany,
|
() => companyStore.selectedCompany,
|
||||||
|
|||||||
@@ -7,9 +7,6 @@
|
|||||||
<i class="pi pi-file-text"></i>
|
<i class="pi pi-file-text"></i>
|
||||||
Facturi
|
Facturi
|
||||||
</h1>
|
</h1>
|
||||||
<p class="page-subtitle">
|
|
||||||
Vizualizați și gestionați facturile pentru compania selectată
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Company Selection -->
|
<!-- Company Selection -->
|
||||||
@@ -32,8 +29,61 @@
|
|||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
<!-- Mobile: Two-row toolbar -->
|
||||||
|
<div v-if="isMobile && companyStore.selectedCompany" class="mobile-toolbar-container">
|
||||||
|
<!-- Row 1: Icon-only action buttons -->
|
||||||
|
<div class="mobile-toolbar-buttons">
|
||||||
|
<Button
|
||||||
|
icon="pi pi-filter"
|
||||||
|
:class="{ 'filter-active': hasActiveFilters }"
|
||||||
|
class="p-button-text"
|
||||||
|
@click="showFilters = !showFilters"
|
||||||
|
v-tooltip.bottom="'Filtre'"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
icon="pi pi-filter-slash"
|
||||||
|
class="p-button-text"
|
||||||
|
@click="clearFilters"
|
||||||
|
v-tooltip.bottom="'Resetează'"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
icon="pi pi-file-excel"
|
||||||
|
class="p-button-text p-button-success"
|
||||||
|
@click="exportExcel"
|
||||||
|
:disabled="!invoicesStore.hasInvoices"
|
||||||
|
v-tooltip.bottom="'Excel'"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
icon="pi pi-file-pdf"
|
||||||
|
class="p-button-text p-button-danger"
|
||||||
|
@click="exportPDF"
|
||||||
|
:disabled="!invoicesStore.hasInvoices"
|
||||||
|
v-tooltip.bottom="'PDF'"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
icon="pi pi-refresh"
|
||||||
|
class="p-button-text"
|
||||||
|
:loading="invoicesStore.isLoading"
|
||||||
|
@click="refreshData"
|
||||||
|
v-tooltip.bottom="'Actualizează'"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Row 2: Totals (unified grid format) -->
|
||||||
|
<div class="mobile-toolbar-totals">
|
||||||
|
<div class="mobile-totals-grid single-total">
|
||||||
|
<div class="total-item">
|
||||||
|
<span class="total-label">Sold Total:</span>
|
||||||
|
<span class="total-value" :class="invoicesStore.totalSoldAll > 0 ? 'positive' : 'negative'">
|
||||||
|
{{ formatCompact(invoicesStore.totalSoldAll) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Filters and Controls -->
|
<!-- Filters and Controls -->
|
||||||
<Card v-if="companyStore.selectedCompany" class="filters-card">
|
<Card v-if="companyStore.selectedCompany && (!isMobile || showFilters)" class="filters-card">
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="form">
|
<div class="form">
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
@@ -96,7 +146,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="filters-actions">
|
<!-- Desktop: Action buttons -->
|
||||||
|
<div v-if="!isMobile" class="filters-actions">
|
||||||
<Button
|
<Button
|
||||||
icon="pi pi-filter-slash"
|
icon="pi pi-filter-slash"
|
||||||
label="Resetează Filtre"
|
label="Resetează Filtre"
|
||||||
@@ -128,9 +179,9 @@
|
|||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<!-- Summary Stats - Compact, right aligned -->
|
<!-- Summary Stats - Compact, right aligned (hidden on mobile - shown in toolbar) -->
|
||||||
<!-- Total sold din TOATE facturile filtrate (nu doar pagina curentă) -->
|
<!-- Total sold din TOATE facturile filtrate (nu doar pagina curentă) -->
|
||||||
<div v-if="companyStore.selectedCompany && invoicesStore.hasInvoices" class="summary-stats-inline">
|
<div v-if="!isMobile && companyStore.selectedCompany && invoicesStore.hasInvoices" class="summary-stats-inline">
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
<span class="stat-label">Total Sold:</span>
|
<span class="stat-label">Total Sold:</span>
|
||||||
<span class="stat-value" :class="invoicesStore.totalSoldAll > 0 ? 'plati' : 'incasari'">
|
<span class="stat-value" :class="invoicesStore.totalSoldAll > 0 ? 'plati' : 'incasari'">
|
||||||
@@ -142,7 +193,33 @@
|
|||||||
<!-- Invoices Table -->
|
<!-- Invoices Table -->
|
||||||
<Card v-if="companyStore.selectedCompany" class="table-card">
|
<Card v-if="companyStore.selectedCompany" class="table-card">
|
||||||
<template #content>
|
<template #content>
|
||||||
|
<!-- Mobile: Card Layout -->
|
||||||
|
<div v-if="isMobile" class="mobile-card-list">
|
||||||
|
<div
|
||||||
|
v-for="invoice in invoicesStore.invoiceList"
|
||||||
|
:key="invoice.nract"
|
||||||
|
class="mobile-data-card"
|
||||||
|
>
|
||||||
|
<div class="card-header">{{ invoice.nume }}</div>
|
||||||
|
<div class="card-row">
|
||||||
|
<span>{{ formatDate(invoice.dataact) }} · {{ invoice.nract }}</span>
|
||||||
|
<span
|
||||||
|
class="card-amount"
|
||||||
|
:class="{ positive: invoice.soldfinal > 0 }"
|
||||||
|
>
|
||||||
|
{{ formatNumber(invoice.soldfinal) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="invoicesStore.invoiceList.length === 0" class="mobile-empty">
|
||||||
|
<i class="pi pi-info-circle"></i>
|
||||||
|
<p>Nu au fost găsite facturi</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Desktop: DataTable -->
|
||||||
<DataTable
|
<DataTable
|
||||||
|
v-if="!isMobile"
|
||||||
:value="invoicesStore.invoiceList"
|
:value="invoicesStore.invoiceList"
|
||||||
:loading="invoicesStore.isLoading"
|
:loading="invoicesStore.isLoading"
|
||||||
:paginator="true"
|
:paginator="true"
|
||||||
@@ -245,7 +322,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted, watch } from "vue";
|
import { ref, computed, onMounted, onUnmounted, watch } from "vue";
|
||||||
import { useToast } from "primevue/usetoast";
|
import { useToast } from "primevue/usetoast";
|
||||||
import { useCompanyStore } from "../stores/companies";
|
import { useCompanyStore } from "../stores/companies";
|
||||||
import { useInvoicesStore } from "../stores/invoices";
|
import { useInvoicesStore } from "../stores/invoices";
|
||||||
@@ -262,6 +339,19 @@ const periodStore = useAccountingPeriodStore();
|
|||||||
// State
|
// State
|
||||||
const selectedCompanyId = ref(companyStore.selectedCompany?.id_firma || null);
|
const selectedCompanyId = ref(companyStore.selectedCompany?.id_firma || null);
|
||||||
|
|
||||||
|
// Mobile state
|
||||||
|
const isMobile = ref(window.innerWidth < 768);
|
||||||
|
const showFilters = ref(false);
|
||||||
|
const actionsMenu = ref(null);
|
||||||
|
|
||||||
|
// Handle window resize
|
||||||
|
const handleResize = () => {
|
||||||
|
isMobile.value = window.innerWidth < 768;
|
||||||
|
if (!isMobile.value) {
|
||||||
|
showFilters.value = false; // Reset when switching to desktop
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const filters = ref({
|
const filters = ref({
|
||||||
type: "CLIENTI",
|
type: "CLIENTI",
|
||||||
paymentStatus: "neachitate", // Default to unpaid invoices
|
paymentStatus: "neachitate", // Default to unpaid invoices
|
||||||
@@ -280,6 +370,43 @@ const accountingPeriodText = computed(() => {
|
|||||||
return periodStore.selectedPeriod?.display_name || "";
|
return periodStore.selectedPeriod?.display_name || "";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Mobile: Check if any filter is active (non-default value)
|
||||||
|
const hasActiveFilters = computed(() => {
|
||||||
|
return (
|
||||||
|
filters.value.type !== "CLIENTI" ||
|
||||||
|
filters.value.paymentStatus !== "neachitate" ||
|
||||||
|
filters.value.searchTerm !== "" ||
|
||||||
|
filters.value.cont !== ""
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mobile: Actions menu items
|
||||||
|
const actionMenuItems = computed(() => [
|
||||||
|
{
|
||||||
|
label: "Resetează Filtre",
|
||||||
|
icon: "pi pi-filter-slash",
|
||||||
|
command: clearFilters,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Export Excel",
|
||||||
|
icon: "pi pi-file-excel",
|
||||||
|
command: exportExcel,
|
||||||
|
disabled: !invoicesStore.hasInvoices,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Export PDF",
|
||||||
|
icon: "pi pi-file-pdf",
|
||||||
|
command: exportPDF,
|
||||||
|
disabled: !invoicesStore.hasInvoices,
|
||||||
|
},
|
||||||
|
{ separator: true },
|
||||||
|
{
|
||||||
|
label: "Actualizează",
|
||||||
|
icon: "pi pi-refresh",
|
||||||
|
command: refreshData,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
// Options
|
// Options
|
||||||
const invoiceTypes = [
|
const invoiceTypes = [
|
||||||
{ label: "Clienți", value: "CLIENTI" },
|
{ label: "Clienți", value: "CLIENTI" },
|
||||||
@@ -308,6 +435,20 @@ const formatNumber = (amount) => {
|
|||||||
}).format(amount);
|
}).format(amount);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Compact format for mobile totals (e.g., "34.922" instead of "34.922,02 RON")
|
||||||
|
const formatCompact = (amount) => {
|
||||||
|
if (!amount || amount === 0) return "0";
|
||||||
|
const absAmount = Math.abs(amount);
|
||||||
|
if (absAmount >= 1000000) {
|
||||||
|
return new Intl.NumberFormat("ro-RO", {
|
||||||
|
maximumFractionDigits: 1,
|
||||||
|
}).format(amount / 1000000) + "M";
|
||||||
|
}
|
||||||
|
return new Intl.NumberFormat("ro-RO", {
|
||||||
|
maximumFractionDigits: 0,
|
||||||
|
}).format(amount);
|
||||||
|
};
|
||||||
|
|
||||||
const formatDate = (dateString) => {
|
const formatDate = (dateString) => {
|
||||||
if (!dateString) return "";
|
if (!dateString) return "";
|
||||||
try {
|
try {
|
||||||
@@ -631,6 +772,9 @@ const exportPDF = async () => {
|
|||||||
|
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
// Add resize listener for mobile detection
|
||||||
|
window.addEventListener("resize", handleResize);
|
||||||
|
|
||||||
// Load companies if not loaded
|
// Load companies if not loaded
|
||||||
if (!companyStore.hasCompanies) {
|
if (!companyStore.hasCompanies) {
|
||||||
await companyStore.loadCompanies();
|
await companyStore.loadCompanies();
|
||||||
@@ -638,6 +782,10 @@ onMounted(async () => {
|
|||||||
// Don't load here - let period watch handle it with immediate: true
|
// Don't load here - let period watch handle it with immediate: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener("resize", handleResize);
|
||||||
|
});
|
||||||
|
|
||||||
// Watch for company changes
|
// Watch for company changes
|
||||||
watch(
|
watch(
|
||||||
() => companyStore.selectedCompany,
|
() => companyStore.selectedCompany,
|
||||||
@@ -675,18 +823,12 @@ watch(
|
|||||||
font-size: 2.5rem;
|
font-size: 2.5rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
margin: 0 0 0.5rem 0;
|
margin: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-subtitle {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
color: var(--text-color-secondary);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.company-selection-card,
|
.company-selection-card,
|
||||||
.filters-card {
|
.filters-card {
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
|
|||||||
@@ -7,10 +7,6 @@
|
|||||||
<i class="pi pi-calculator"></i>
|
<i class="pi pi-calculator"></i>
|
||||||
Balanță de Verificare
|
Balanță de Verificare
|
||||||
</h1>
|
</h1>
|
||||||
<p class="page-subtitle">
|
|
||||||
{{ currentPeriodText }} -
|
|
||||||
{{ companyStore.selectedCompany?.name || "Selectați companie" }}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Company Selection -->
|
<!-- Company Selection -->
|
||||||
@@ -33,8 +29,63 @@
|
|||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
<!-- Mobile: Two-row toolbar -->
|
||||||
|
<div v-if="isMobile && companyStore.selectedCompany" class="mobile-toolbar-container">
|
||||||
|
<!-- Row 1: Icon-only action buttons -->
|
||||||
|
<div class="mobile-toolbar-buttons">
|
||||||
|
<Button
|
||||||
|
icon="pi pi-filter"
|
||||||
|
:class="{ 'filter-active': hasActiveFilters }"
|
||||||
|
class="p-button-text"
|
||||||
|
@click="showFilters = !showFilters"
|
||||||
|
v-tooltip.bottom="'Filtre'"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
icon="pi pi-filter-slash"
|
||||||
|
class="p-button-text"
|
||||||
|
@click="clearFilters"
|
||||||
|
v-tooltip.bottom="'Resetează'"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
icon="pi pi-file-excel"
|
||||||
|
class="p-button-text p-button-success"
|
||||||
|
@click="exportExcel"
|
||||||
|
:disabled="!trialBalanceStore.hasData"
|
||||||
|
v-tooltip.bottom="'Excel'"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
icon="pi pi-file-pdf"
|
||||||
|
class="p-button-text p-button-danger"
|
||||||
|
@click="exportPDF"
|
||||||
|
:disabled="!trialBalanceStore.hasData"
|
||||||
|
v-tooltip.bottom="'PDF'"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
icon="pi pi-refresh"
|
||||||
|
class="p-button-text"
|
||||||
|
:loading="trialBalanceStore.isLoading"
|
||||||
|
@click="refreshData"
|
||||||
|
v-tooltip.bottom="'Actualizează'"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Row 2: Totals (unified grid format) -->
|
||||||
|
<div class="mobile-toolbar-totals">
|
||||||
|
<div class="mobile-totals-grid two-totals">
|
||||||
|
<div class="total-item">
|
||||||
|
<span class="total-label">Sold D:</span>
|
||||||
|
<span class="total-value">{{ formatCompact(trialBalanceStore.totals.total_sold_final_debit) }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="total-item">
|
||||||
|
<span class="total-label">Sold C:</span>
|
||||||
|
<span class="total-value">{{ formatCompact(trialBalanceStore.totals.total_sold_final_credit) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Filters Section -->
|
<!-- Filters Section -->
|
||||||
<Card v-if="companyStore.selectedCompany" class="filters-card">
|
<Card v-if="companyStore.selectedCompany && (!isMobile || showFilters)" class="filters-card">
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="form">
|
<div class="form">
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
@@ -65,7 +116,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-actions">
|
<!-- Desktop: Action buttons -->
|
||||||
|
<div v-if="!isMobile" class="form-actions">
|
||||||
<Button
|
<Button
|
||||||
icon="pi pi-filter-slash"
|
icon="pi pi-filter-slash"
|
||||||
label="Resetează Filtre"
|
label="Resetează Filtre"
|
||||||
@@ -97,9 +149,9 @@
|
|||||||
</template>
|
</template>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<!-- Summary Totals - Uses shared stats.css -->
|
<!-- Summary Totals - Uses shared stats.css (hidden on mobile - compact in toolbar) -->
|
||||||
<!-- Totaluri din TOATE înregistrările filtrate (nu doar pagina curentă) -->
|
<!-- Totaluri din TOATE înregistrările filtrate (nu doar pagina curentă) -->
|
||||||
<div v-if="companyStore.selectedCompany && trialBalanceStore.hasData" class="summary-stats-inline">
|
<div v-if="!isMobile && companyStore.selectedCompany && trialBalanceStore.hasData" class="summary-stats-inline">
|
||||||
<div class="stat-item">
|
<div class="stat-item">
|
||||||
<span class="stat-label">Sume Prec. D:</span>
|
<span class="stat-label">Sume Prec. D:</span>
|
||||||
<span class="stat-value">{{ formatCurrency(trialBalanceStore.totals.total_sold_precedent_debit) }}</span>
|
<span class="stat-value">{{ formatCurrency(trialBalanceStore.totals.total_sold_precedent_debit) }}</span>
|
||||||
@@ -129,7 +181,34 @@
|
|||||||
<!-- Trial Balance Table -->
|
<!-- Trial Balance Table -->
|
||||||
<Card v-if="companyStore.selectedCompany" class="table-card">
|
<Card v-if="companyStore.selectedCompany" class="table-card">
|
||||||
<template #content>
|
<template #content>
|
||||||
|
<!-- Mobile: Card Layout -->
|
||||||
|
<div v-if="isMobile" class="mobile-card-list">
|
||||||
|
<div
|
||||||
|
v-for="account in trialBalanceStore.trialBalanceData.filter(a => a.sold_final_debit > 0 || a.sold_final_credit > 0)"
|
||||||
|
:key="account.cont"
|
||||||
|
class="mobile-data-card"
|
||||||
|
>
|
||||||
|
<div class="card-header">
|
||||||
|
<strong>{{ account.cont }}</strong> {{ truncate(account.denumire, 30) }}
|
||||||
|
</div>
|
||||||
|
<div class="card-row">
|
||||||
|
<span></span>
|
||||||
|
<span class="card-amount">
|
||||||
|
{{ account.sold_final_debit > 0
|
||||||
|
? formatCurrency(account.sold_final_debit) + ' D'
|
||||||
|
: formatCurrency(account.sold_final_credit) + ' C' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="trialBalanceStore.trialBalanceData.filter(a => a.sold_final_debit > 0 || a.sold_final_credit > 0).length === 0" class="mobile-empty">
|
||||||
|
<i class="pi pi-info-circle"></i>
|
||||||
|
<p>Nu au fost găsite date</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Desktop: DataTable -->
|
||||||
<DataTable
|
<DataTable
|
||||||
|
v-if="!isMobile"
|
||||||
:value="trialBalanceStore.trialBalanceData"
|
:value="trialBalanceStore.trialBalanceData"
|
||||||
:loading="trialBalanceStore.isLoading"
|
:loading="trialBalanceStore.isLoading"
|
||||||
:paginator="true"
|
:paginator="true"
|
||||||
@@ -263,7 +342,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted, watch } from "vue";
|
import { ref, computed, onMounted, onUnmounted, watch } from "vue";
|
||||||
import { useToast } from "primevue/usetoast";
|
import { useToast } from "primevue/usetoast";
|
||||||
import { useCompanyStore } from "../stores/companies";
|
import { useCompanyStore } from "../stores/companies";
|
||||||
import { useTrialBalanceStore } from "../stores/trialBalance";
|
import { useTrialBalanceStore } from "../stores/trialBalance";
|
||||||
@@ -278,6 +357,19 @@ const periodStore = useAccountingPeriodStore();
|
|||||||
// State
|
// State
|
||||||
const selectedCompanyId = ref(companyStore.selectedCompany?.id_firma || null);
|
const selectedCompanyId = ref(companyStore.selectedCompany?.id_firma || null);
|
||||||
|
|
||||||
|
// Mobile state
|
||||||
|
const isMobile = ref(window.innerWidth < 768);
|
||||||
|
const showFilters = ref(false);
|
||||||
|
const actionsMenu = ref(null);
|
||||||
|
|
||||||
|
// Handle window resize
|
||||||
|
const handleResize = () => {
|
||||||
|
isMobile.value = window.innerWidth < 768;
|
||||||
|
if (!isMobile.value) {
|
||||||
|
showFilters.value = false; // Reset when switching to desktop
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const localFilters = ref({
|
const localFilters = ref({
|
||||||
cont: "",
|
cont: "",
|
||||||
denumire: "",
|
denumire: "",
|
||||||
@@ -289,6 +381,38 @@ const currentPeriodText = computed(() => {
|
|||||||
return periodStore.selectedPeriod?.display_name || "";
|
return periodStore.selectedPeriod?.display_name || "";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Mobile: Check if any filter is active (non-default value)
|
||||||
|
const hasActiveFilters = computed(() => {
|
||||||
|
return localFilters.value.cont !== "" || localFilters.value.denumire !== "";
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mobile: Actions menu items
|
||||||
|
const actionMenuItems = computed(() => [
|
||||||
|
{
|
||||||
|
label: "Resetează Filtre",
|
||||||
|
icon: "pi pi-filter-slash",
|
||||||
|
command: clearFilters,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Export Excel",
|
||||||
|
icon: "pi pi-file-excel",
|
||||||
|
command: exportExcel,
|
||||||
|
disabled: !trialBalanceStore.hasData,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Export PDF",
|
||||||
|
icon: "pi pi-file-pdf",
|
||||||
|
command: exportPDF,
|
||||||
|
disabled: !trialBalanceStore.hasData,
|
||||||
|
},
|
||||||
|
{ separator: true },
|
||||||
|
{
|
||||||
|
label: "Actualizează",
|
||||||
|
icon: "pi pi-refresh",
|
||||||
|
command: refreshData,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
const formatCurrency = (amount) => {
|
const formatCurrency = (amount) => {
|
||||||
if (!amount || amount === 0) return "0,00";
|
if (!amount || amount === 0) return "0,00";
|
||||||
@@ -298,6 +422,26 @@ const formatCurrency = (amount) => {
|
|||||||
}).format(amount);
|
}).format(amount);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Compact format for mobile totals (e.g., "449.881" instead of "449.881,12")
|
||||||
|
const formatCompact = (amount) => {
|
||||||
|
if (!amount || amount === 0) return "0";
|
||||||
|
const absAmount = Math.abs(amount);
|
||||||
|
if (absAmount >= 1000000) {
|
||||||
|
return new Intl.NumberFormat("ro-RO", {
|
||||||
|
maximumFractionDigits: 1,
|
||||||
|
}).format(amount / 1000000) + "M";
|
||||||
|
}
|
||||||
|
return new Intl.NumberFormat("ro-RO", {
|
||||||
|
maximumFractionDigits: 0,
|
||||||
|
}).format(amount);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Truncate text for mobile cards
|
||||||
|
const truncate = (text, maxLength) => {
|
||||||
|
if (!text || text.length <= maxLength) return text;
|
||||||
|
return text.substring(0, maxLength) + "...";
|
||||||
|
};
|
||||||
|
|
||||||
const handleCompanyChange = async () => {
|
const handleCompanyChange = async () => {
|
||||||
if (!selectedCompanyId.value) return;
|
if (!selectedCompanyId.value) return;
|
||||||
|
|
||||||
@@ -715,6 +859,9 @@ const exportPDF = async () => {
|
|||||||
|
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
// Add resize listener for mobile detection
|
||||||
|
window.addEventListener("resize", handleResize);
|
||||||
|
|
||||||
// Load companies if not loaded
|
// Load companies if not loaded
|
||||||
if (!companyStore.hasCompanies) {
|
if (!companyStore.hasCompanies) {
|
||||||
await companyStore.loadCompanies();
|
await companyStore.loadCompanies();
|
||||||
@@ -734,6 +881,10 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener("resize", handleResize);
|
||||||
|
});
|
||||||
|
|
||||||
// Watch for company changes
|
// Watch for company changes
|
||||||
watch(
|
watch(
|
||||||
() => companyStore.selectedCompany,
|
() => companyStore.selectedCompany,
|
||||||
|
|||||||
Reference in New Issue
Block a user