feat(unified-mobile-desktop-ui): Complete US-509 - Fix Detailed Invoices - Grupuri Expandabile Desktop
Implemented by Ralph autonomous loop. Iteration: 9 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -202,8 +202,8 @@
|
||||
"npm run build passes",
|
||||
"Verify in browser desktop: grupurile se extind la click"
|
||||
],
|
||||
"passes": false,
|
||||
"notes": ""
|
||||
"passes": true,
|
||||
"notes": "Completed in iteration 9"
|
||||
},
|
||||
{
|
||||
"id": "US-510",
|
||||
|
||||
@@ -118,3 +118,9 @@ Design Reference: src/modules/reports/views/InvoicesView.vue
|
||||
[2026-01-12 22:42:56] Working on story: US-508
|
||||
[2026-01-12 22:42:56] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_8_US-508.log)
|
||||
[2026-01-12 22:45:25] SUCCESS: Story US-508 passed!
|
||||
[2026-01-12 22:45:25] Changes committed
|
||||
[2026-01-12 22:45:25] Progress: 8/19 stories completed
|
||||
[2026-01-12 22:45:27] === Iteration 9/100 ===
|
||||
[2026-01-12 22:45:27] Working on story: US-509
|
||||
[2026-01-12 22:45:27] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_9_US-509.log)
|
||||
[2026-01-12 22:48:38] SUCCESS: Story US-509 passed!
|
||||
|
||||
@@ -134,80 +134,142 @@
|
||||
<div v-else class="data-section">
|
||||
<!-- Desktop Table -->
|
||||
<div v-if="!isMobile" class="table-wrapper">
|
||||
<!-- Treasury DataTable (no expansion needed) -->
|
||||
<DataTable
|
||||
:value="selectedType === 'treasury' ? paginatedData : paginatedGroups"
|
||||
v-if="selectedType === 'treasury'"
|
||||
:value="paginatedData"
|
||||
:loading="isLoading"
|
||||
stripedRows
|
||||
class="p-datatable-sm"
|
||||
>
|
||||
<!-- Treasury columns -->
|
||||
<template v-if="selectedType === 'treasury'">
|
||||
<Column field="cont" header="Cont" sortable></Column>
|
||||
<Column field="nume_cont" header="Nume Cont" sortable></Column>
|
||||
<Column field="sold" header="Sold" sortable>
|
||||
<template #body="slotProps">
|
||||
<span class="font-mono">{{ formatCurrency(slotProps.data.sold) }}</span>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="valuta" header="Valută" sortable></Column>
|
||||
<Column field="tip" header="Tip" sortable></Column>
|
||||
</template>
|
||||
<Column field="cont" header="Cont" sortable></Column>
|
||||
<Column field="nume_cont" header="Nume Cont" sortable></Column>
|
||||
<Column field="sold" header="Sold" sortable>
|
||||
<template #body="slotProps">
|
||||
<span class="font-mono">{{ formatCurrency(slotProps.data.sold) }}</span>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="valuta" header="Valută" sortable></Column>
|
||||
<Column field="tip" header="Tip" sortable></Column>
|
||||
</DataTable>
|
||||
|
||||
<!-- Clients/Suppliers columns -->
|
||||
<template v-else>
|
||||
<Column :field="selectedType === 'clients' ? 'client' : 'furnizor'" :header="selectedType === 'clients' ? 'Client' : 'Furnizor'" sortable>
|
||||
<template #body="slotProps">
|
||||
<div class="name-cell">
|
||||
<strong>{{ slotProps.data.name }}</strong>
|
||||
<span v-if="slotProps.data.facturi?.length > 1" class="count-badge">
|
||||
({{ slotProps.data.facturi.length }})
|
||||
<!-- Clients/Suppliers Expandable Groups -->
|
||||
<div v-else class="expandable-groups-table">
|
||||
<!-- Table Header -->
|
||||
<div class="groups-table-header">
|
||||
<div class="header-cell expand-col"></div>
|
||||
<div class="header-cell name-col">{{ selectedType === 'clients' ? 'Client' : 'Furnizor' }}</div>
|
||||
<div class="header-cell">Nr. Document</div>
|
||||
<div class="header-cell">Data Document</div>
|
||||
<div class="header-cell">Data Scadență</div>
|
||||
<div class="header-cell text-right">Facturat</div>
|
||||
<div class="header-cell text-right">{{ selectedType === 'clients' ? 'Încasat' : 'Achitat' }}</div>
|
||||
<div class="header-cell text-right">Sold</div>
|
||||
</div>
|
||||
|
||||
<!-- Table Body - Groups -->
|
||||
<div class="groups-table-body">
|
||||
<template v-for="group in paginatedGroups" :key="group.name">
|
||||
<!-- Group Header Row (clickable for expand) -->
|
||||
<div
|
||||
class="group-row"
|
||||
:class="{
|
||||
'expandable': group.facturi.length > 1,
|
||||
'expanded': isGroupExpanded(group.name)
|
||||
}"
|
||||
@click="group.facturi.length > 1 && toggleGroup(group.name)"
|
||||
>
|
||||
<div class="row-cell expand-col">
|
||||
<i
|
||||
v-if="group.facturi.length > 1"
|
||||
class="expand-icon pi pi-chevron-right"
|
||||
:class="{ 'rotated': isGroupExpanded(group.name) }"
|
||||
></i>
|
||||
</div>
|
||||
<div class="row-cell name-col">
|
||||
<strong>{{ group.name }}</strong>
|
||||
<span v-if="group.facturi.length > 1" class="count-badge">
|
||||
({{ group.facturi.length }} facturi)
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="numar_document" header="Nr. Document">
|
||||
<template #body="slotProps">
|
||||
{{ slotProps.data.facturi?.length === 1 ? slotProps.data.facturi[0].numar_document : '-' }}
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="data_document" header="Data Document">
|
||||
<template #body="slotProps">
|
||||
{{ slotProps.data.facturi?.length === 1 ? formatDate(slotProps.data.facturi[0].data_document) : '-' }}
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="data_scadenta" header="Data Scadență">
|
||||
<template #body="slotProps">
|
||||
{{ slotProps.data.facturi?.length === 1 ? formatDate(slotProps.data.facturi[0].data_scadenta) : '-' }}
|
||||
</template>
|
||||
</Column>
|
||||
<Column header="Facturat">
|
||||
<template #body="slotProps">
|
||||
<span class="font-mono">
|
||||
{{ slotProps.data.facturi?.length === 1 ? formatCurrency(slotProps.data.facturi[0].facturat) : '-' }}
|
||||
</span>
|
||||
</template>
|
||||
</Column>
|
||||
<Column :header="selectedType === 'clients' ? 'Încasat' : 'Achitat'">
|
||||
<template #body="slotProps">
|
||||
<span class="font-mono">
|
||||
{{ slotProps.data.facturi?.length === 1
|
||||
? formatCurrency(slotProps.data.facturi[0][selectedType === 'clients' ? 'incasat' : 'achitat'])
|
||||
: '-' }}
|
||||
</span>
|
||||
</template>
|
||||
</Column>
|
||||
<Column field="totalSold" header="Sold" sortable>
|
||||
<template #body="slotProps">
|
||||
<span
|
||||
class="font-mono font-bold"
|
||||
:class="{ 'sold-restant': slotProps.data.hasRestant }"
|
||||
<div class="row-cell">
|
||||
{{ group.facturi.length === 1 ? group.facturi[0].numar_document : '-' }}
|
||||
</div>
|
||||
<div class="row-cell">
|
||||
{{ group.facturi.length === 1 ? formatDate(group.facturi[0].data_document) : '-' }}
|
||||
</div>
|
||||
<div class="row-cell">
|
||||
{{ group.facturi.length === 1 ? formatDate(group.facturi[0].data_scadenta) : '-' }}
|
||||
</div>
|
||||
<div class="row-cell text-right">
|
||||
<span class="font-mono">
|
||||
{{ group.facturi.length === 1 ? formatCurrency(group.facturi[0].facturat) : '-' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="row-cell text-right">
|
||||
<span class="font-mono">
|
||||
{{ group.facturi.length === 1
|
||||
? formatCurrency(group.facturi[0][selectedType === 'clients' ? 'incasat' : 'achitat'])
|
||||
: '-' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="row-cell text-right">
|
||||
<span
|
||||
class="font-mono font-bold"
|
||||
:class="{ 'sold-restant': group.hasRestant }"
|
||||
>
|
||||
{{ formatCurrency(group.totalSold) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Expanded Invoice Rows -->
|
||||
<Transition name="expand">
|
||||
<div
|
||||
v-if="group.facturi.length > 1 && isGroupExpanded(group.name)"
|
||||
class="expanded-invoices"
|
||||
>
|
||||
{{ formatCurrency(slotProps.data.totalSold) }}
|
||||
</span>
|
||||
</template>
|
||||
</Column>
|
||||
</template>
|
||||
</DataTable>
|
||||
<div
|
||||
v-for="(factura, idx) in group.facturi"
|
||||
:key="`${group.name}-invoice-${idx}`"
|
||||
class="invoice-row"
|
||||
>
|
||||
<div class="row-cell expand-col"></div>
|
||||
<div class="row-cell name-col sub-invoice-indicator">
|
||||
<span class="invoice-connector"></span>
|
||||
<span class="invoice-doc-label">Factura</span>
|
||||
</div>
|
||||
<div class="row-cell">{{ factura.numar_document }}</div>
|
||||
<div class="row-cell">{{ formatDate(factura.data_document) }}</div>
|
||||
<div class="row-cell">{{ formatDate(factura.data_scadenta) }}</div>
|
||||
<div class="row-cell text-right">
|
||||
<span class="font-mono">{{ formatCurrency(factura.facturat) }}</span>
|
||||
</div>
|
||||
<div class="row-cell text-right">
|
||||
<span class="font-mono">
|
||||
{{ formatCurrency(factura[selectedType === 'clients' ? 'incasat' : 'achitat']) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="row-cell text-right">
|
||||
<span
|
||||
class="font-mono"
|
||||
:class="{ 'sold-restant': factura.status === 'Restant' }"
|
||||
>
|
||||
{{ formatCurrency(factura.sold) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<!-- Empty state -->
|
||||
<div v-if="paginatedGroups.length === 0" class="empty-table-state">
|
||||
<i class="pi pi-inbox"></i>
|
||||
<p>Nu există facturi pentru criteriile selectate</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile Cards -->
|
||||
@@ -408,7 +470,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { ref, computed, onMounted, onUnmounted, watch, Transition } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import Button from 'primevue/button'
|
||||
import Dropdown from 'primevue/dropdown'
|
||||
@@ -1030,6 +1092,221 @@ onUnmounted(() => {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
US-509: Expandable Groups Table (Desktop)
|
||||
============================================ */
|
||||
.expandable-groups-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Table Header */
|
||||
.groups-table-header {
|
||||
display: grid;
|
||||
grid-template-columns: 40px 2fr 1fr 1fr 1fr 1fr 1fr 1fr;
|
||||
gap: var(--space-xs);
|
||||
padding: var(--space-md);
|
||||
background: var(--surface-hover);
|
||||
border-bottom: 1px solid var(--surface-border);
|
||||
font-weight: var(--font-semibold);
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.header-cell {
|
||||
padding: var(--space-xs) var(--space-sm);
|
||||
}
|
||||
|
||||
.header-cell.expand-col {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.header-cell.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* Table Body */
|
||||
.groups-table-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Group Row (Parent) */
|
||||
.group-row {
|
||||
display: grid;
|
||||
grid-template-columns: 40px 2fr 1fr 1fr 1fr 1fr 1fr 1fr;
|
||||
gap: var(--space-xs);
|
||||
padding: var(--space-sm) var(--space-md);
|
||||
border-bottom: 1px solid var(--surface-border);
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-color);
|
||||
transition: background var(--transition-fast);
|
||||
}
|
||||
|
||||
.group-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.group-row.expandable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.group-row.expandable:hover {
|
||||
background: var(--surface-hover);
|
||||
}
|
||||
|
||||
.group-row.expanded {
|
||||
background: var(--primary-50);
|
||||
border-bottom-color: var(--primary-100);
|
||||
}
|
||||
|
||||
/* Row cells */
|
||||
.row-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: var(--space-xs) var(--space-sm);
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.row-cell.expand-col {
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.row-cell.name-col {
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.row-cell.text-right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
/* Expand Icon with rotation animation */
|
||||
.expand-icon {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-color-secondary);
|
||||
transition: transform var(--transition-normal);
|
||||
}
|
||||
|
||||
.expand-icon.rotated {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
/* Expanded Invoices Container */
|
||||
.expanded-invoices {
|
||||
background: var(--surface-ground);
|
||||
border-bottom: 1px solid var(--surface-border);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Individual Invoice Row */
|
||||
.invoice-row {
|
||||
display: grid;
|
||||
grid-template-columns: 40px 2fr 1fr 1fr 1fr 1fr 1fr 1fr;
|
||||
gap: var(--space-xs);
|
||||
padding: var(--space-xs) var(--space-md);
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-color);
|
||||
border-bottom: 1px solid var(--surface-border);
|
||||
}
|
||||
|
||||
.invoice-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.invoice-row:hover {
|
||||
background: var(--surface-hover);
|
||||
}
|
||||
|
||||
/* Sub-invoice indicator (connector line + label) */
|
||||
.sub-invoice-indicator {
|
||||
position: relative;
|
||||
padding-left: var(--space-lg) !important;
|
||||
}
|
||||
|
||||
.invoice-connector {
|
||||
position: absolute;
|
||||
left: var(--space-sm);
|
||||
top: 50%;
|
||||
width: var(--space-md);
|
||||
height: 1px;
|
||||
background: var(--surface-border);
|
||||
}
|
||||
|
||||
.invoice-connector::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: -8px;
|
||||
width: 1px;
|
||||
height: 16px;
|
||||
background: var(--surface-border);
|
||||
}
|
||||
|
||||
.invoice-doc-label {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-color-secondary);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Expand/Collapse Animation */
|
||||
.expand-enter-active,
|
||||
.expand-leave-active {
|
||||
transition: all var(--transition-normal);
|
||||
transform-origin: top;
|
||||
}
|
||||
|
||||
.expand-enter-from,
|
||||
.expand-leave-to {
|
||||
opacity: 0;
|
||||
max-height: 0;
|
||||
transform: scaleY(0);
|
||||
}
|
||||
|
||||
.expand-enter-to,
|
||||
.expand-leave-from {
|
||||
opacity: 1;
|
||||
max-height: 1000px;
|
||||
transform: scaleY(1);
|
||||
}
|
||||
|
||||
/* Empty table state */
|
||||
.empty-table-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--space-2xl);
|
||||
text-align: center;
|
||||
color: var(--text-color-secondary);
|
||||
}
|
||||
|
||||
.empty-table-state .pi {
|
||||
font-size: 48px;
|
||||
margin-bottom: var(--space-md);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* Dark mode support for expandable groups */
|
||||
[data-theme="dark"] .group-row.expanded {
|
||||
background: var(--primary-900);
|
||||
border-bottom-color: var(--primary-800);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .expanded-invoices {
|
||||
background: var(--surface-100);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme]) .group-row.expanded {
|
||||
background: var(--primary-900);
|
||||
border-bottom-color: var(--primary-800);
|
||||
}
|
||||
|
||||
:root:not([data-theme]) .expanded-invoices {
|
||||
background: var(--surface-100);
|
||||
}
|
||||
}
|
||||
|
||||
.font-mono {
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user