feat(unified-mobile-material-design): Complete US-107 - InvoicesView Mobile Material Design

Implemented by Ralph autonomous loop.
Iteration: 11

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-01-12 10:11:07 +00:00
parent 1e6981d2d2
commit fb0334e270
3 changed files with 460 additions and 73 deletions

View File

@@ -185,8 +185,8 @@
"Verify in browser că arată consistent cu bonuri",
"npm run build passes"
],
"passes": false,
"notes": ""
"passes": true,
"notes": "Completed in iteration 11"
},
{
"id": "US-108",

View File

@@ -66,3 +66,9 @@ Mon Jan 12 09:44:54 AM UTC 2026
[2026-01-12 10:04:33] Working on story: US-106
[2026-01-12 10:04:33] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_10_US-106.log)
[2026-01-12 10:07:38] SUCCESS: Story US-106 passed!
[2026-01-12 10:07:38] Changes committed
[2026-01-12 10:07:38] Progress: 9/20 stories completed
[2026-01-12 10:07:40] === Iteration 11/100 ===
[2026-01-12 10:07:40] Working on story: US-107
[2026-01-12 10:07:40] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_11_US-107.log)
[2026-01-12 10:11:07] SUCCESS: Story US-107 passed!

View File

@@ -1,8 +1,116 @@
<template>
<div class="app-container">
<div class="app-container" :class="{ 'mobile-layout': isMobile }">
<!-- US-107: Mobile Material Design Top Bar -->
<MobileTopBar
v-if="isMobile"
title="Facturi"
:show-menu="true"
:actions="mobileTopBarActions"
@menu-click="toggleMobileMenu"
@action-click="handleTopBarAction"
/>
<!-- US-107: Mobile Hamburger Menu -->
<Sidebar v-model:visible="mobileMenuVisible" position="left" class="mobile-sidebar">
<template #header>
<div class="sidebar-header">
<span class="sidebar-title">ROA2WEB</span>
</div>
</template>
<div class="sidebar-menu">
<router-link to="/data-entry" class="sidebar-item">
<i class="pi pi-receipt"></i>
<span>Bonuri</span>
</router-link>
<router-link to="/reports/invoices" class="sidebar-item active">
<i class="pi pi-file-text"></i>
<span>Facturi</span>
</router-link>
<router-link to="/reports/dashboard" class="sidebar-item">
<i class="pi pi-chart-bar"></i>
<span>Rapoarte</span>
</router-link>
<router-link to="/data-entry/ocr-metrics" class="sidebar-item">
<i class="pi pi-cog"></i>
<span>Setări</span>
</router-link>
</div>
</Sidebar>
<!-- US-107: Filter BottomSheet for mobile -->
<BottomSheet v-model="showFilters">
<h3 class="bottom-sheet-title">Filtre</h3>
<div class="bottom-sheet-filters">
<!-- Invoice Type -->
<div class="form-group">
<label class="form-label">Tip Factură</label>
<Dropdown
v-model="filters.type"
:options="invoiceTypes"
option-label="label"
option-value="value"
placeholder="Tip factură"
class="w-full"
@change="handleFilterChange"
/>
</div>
<!-- Payment Status -->
<div class="form-group">
<label class="form-label">Status Plată</label>
<Dropdown
v-model="filters.paymentStatus"
:options="paymentStatusOptions"
option-label="label"
option-value="value"
placeholder="Status plată"
class="w-full"
@change="handleFilterChange"
/>
</div>
<!-- Search -->
<div class="form-group">
<label class="form-label">Căutare</label>
<InputText
v-model="filters.searchTerm"
placeholder="Căutați după număr, partener..."
class="w-full"
@input="handleSearchChange"
/>
</div>
<!-- Cont Filter -->
<div class="form-group">
<label class="form-label">Cont</label>
<InputText
v-model="filters.cont"
placeholder="Filtru cont (ex: 4111)"
class="w-full"
@input="handleSearchChange"
/>
</div>
<!-- Bottom sheet actions -->
<div class="bottom-sheet-actions">
<Button
icon="pi pi-filter-slash"
label="Resetează"
class="p-button-outlined p-button-secondary"
@click="clearFilters; showFilters = false"
/>
<Button
icon="pi pi-check"
label="Aplică"
@click="showFilters = false"
/>
</div>
</div>
</BottomSheet>
<div class="invoices">
<!-- Header Section -->
<div class="page-header">
<!-- Header Section - Desktop only -->
<div class="page-header" v-if="!isMobile">
<h1 class="page-title">
<i class="pi pi-file-text"></i>
Facturi
@@ -29,61 +137,18 @@
</template>
</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>
<!-- US-107: Mobile Totals Summary (actions moved to MobileTopBar) -->
<div v-if="isMobile && companyStore.selectedCompany && invoicesStore.hasInvoices" class="mobile-totals-bar">
<div class="mobile-totals-content">
<span class="total-label">Sold Total:</span>
<span class="total-value" :class="invoicesStore.totalSoldAll > 0 ? 'positive' : 'negative'">
{{ formatCompact(invoicesStore.totalSoldAll) }}
</span>
</div>
</div>
<!-- Filters and Controls -->
<Card v-if="companyStore.selectedCompany && (!isMobile || showFilters)" class="filters-card">
<!-- Filters and Controls - Desktop only (mobile uses BottomSheet) -->
<Card v-if="companyStore.selectedCompany && !isMobile" class="filters-card">
<template #content>
<div class="form">
<div class="form-row">
@@ -318,6 +383,12 @@
</template>
</Card>
</div>
<!-- US-107: Mobile Bottom Navigation -->
<MobileBottomNav
v-if="isMobile"
:items="mobileBottomNavItems"
/>
</div>
</template>
@@ -331,6 +402,12 @@ import { format } from "date-fns";
import { ro } from "date-fns/locale";
import { exportToExcel, exportToPDF } from "@reports/utils/exportUtils";
// US-107: Mobile Material Design components
import MobileTopBar from "@shared/components/mobile/MobileTopBar.vue";
import MobileBottomNav from "@shared/components/mobile/MobileBottomNav.vue";
import BottomSheet from "@shared/components/mobile/BottomSheet.vue";
import Sidebar from "primevue/sidebar";
const toast = useToast();
const companyStore = useCompanyStore();
const invoicesStore = useInvoicesStore();
@@ -342,8 +419,53 @@ const selectedCompanyId = ref(companyStore.selectedCompany?.id_firma || null);
// Mobile state
const isMobile = ref(window.innerWidth < 768);
const showFilters = ref(false);
const mobileMenuVisible = ref(false);
const actionsMenu = ref(null);
// US-107: Toggle mobile hamburger menu
const toggleMobileMenu = () => {
mobileMenuVisible.value = !mobileMenuVisible.value;
};
// US-107: Mobile TopBar actions (refresh + export)
const mobileTopBarActions = computed(() => [
{
icon: "pi pi-filter",
label: "Filtre",
tooltip: "Filtre",
active: hasActiveFilters.value
},
{
icon: "pi pi-refresh",
label: "Actualizează",
tooltip: "Actualizează"
},
{
icon: "pi pi-download",
label: "Export",
tooltip: "Export Excel"
}
]);
// US-107: Handle top bar action clicks
const handleTopBarAction = (action) => {
if (action.icon === "pi pi-filter") {
showFilters.value = !showFilters.value;
} else if (action.icon === "pi pi-refresh") {
refreshData();
} else if (action.icon === "pi pi-download") {
exportExcel();
}
};
// US-107: Bottom nav items for MobileBottomNav component
const mobileBottomNavItems = computed(() => [
{ to: "/data-entry", icon: "pi pi-receipt", label: "Bonuri" },
{ to: "/reports/invoices", icon: "pi pi-file-text", label: "Facturi", active: true },
{ to: "/reports/dashboard", icon: "pi pi-chart-bar", label: "Rapoarte" },
{ to: "/data-entry/ocr-metrics", icon: "pi pi-cog", label: "Setări" }
]);
// Handle window resize
const handleResize = () => {
isMobile.value = window.innerWidth < 768;
@@ -809,29 +931,41 @@ watch(
</script>
<style scoped>
/* ================================================
US-107: InvoicesView Mobile Material Design Styles
================================================ */
.invoices {
max-width: 1400px;
margin: 0 auto;
padding: 2rem;
padding: var(--space-xl);
}
/* Mobile layout adjustments for top/bottom bars */
.mobile-layout .invoices {
padding-top: calc(56px + var(--space-md)); /* Account for fixed MobileTopBar */
padding-bottom: calc(56px + var(--space-md)); /* Account for fixed MobileBottomNav */
padding-left: var(--space-md);
padding-right: var(--space-md);
}
.page-header {
margin-bottom: 2rem;
margin-bottom: var(--space-xl);
}
.page-title {
font-size: 2.5rem;
font-weight: 700;
font-size: var(--text-4xl);
font-weight: var(--font-bold);
color: var(--text-color);
margin: 0;
display: flex;
align-items: center;
gap: 0.75rem;
gap: var(--space-sm);
}
.company-selection-card,
.filters-card {
margin-bottom: 2rem;
margin-bottom: var(--space-xl);
}
.search-col {
@@ -840,14 +974,14 @@ watch(
.filters-actions {
display: flex;
gap: 1rem;
gap: var(--space-md);
justify-content: flex-end;
padding-top: 1rem;
padding-top: var(--space-md);
border-top: 1px solid var(--surface-border);
}
.table-card {
margin-bottom: 2rem;
margin-bottom: var(--space-xl);
}
.no-data,
@@ -856,14 +990,14 @@ watch(
flex-direction: column;
align-items: center;
justify-content: center;
padding: 3rem;
padding: var(--space-3xl);
color: var(--text-color-secondary);
}
.no-data i,
.loading-table i {
font-size: 2rem;
margin-bottom: 0.5rem;
font-size: var(--text-3xl);
margin-bottom: var(--space-sm);
}
.text-right {
@@ -878,16 +1012,263 @@ watch(
display: block;
}
/* Enhanced striped rows - moved to vendor/primevue-overrides.css with design tokens */
/* ================================================
US-107: Mobile Totals Bar
================================================ */
.mobile-totals-bar {
background: var(--surface-card);
border-bottom: 1px solid var(--surface-border);
padding: var(--space-sm) var(--space-md);
margin-bottom: var(--space-md);
border-radius: var(--radius-md);
}
.mobile-totals-content {
display: flex;
justify-content: space-between;
align-items: center;
}
.mobile-totals-bar .total-label {
font-size: var(--text-sm);
color: var(--text-color-secondary);
font-weight: var(--font-medium);
}
.mobile-totals-bar .total-value {
font-size: var(--text-lg);
font-weight: var(--font-bold);
}
.mobile-totals-bar .total-value.positive {
color: var(--green-600);
}
.mobile-totals-bar .total-value.negative {
color: var(--red-600);
}
/* ================================================
US-107: Mobile Card List (Invoice Cards)
================================================ */
.mobile-card-list {
display: flex;
flex-direction: column;
gap: var(--space-sm);
}
.mobile-data-card {
background: var(--surface-card);
border: 1px solid var(--surface-border);
border-radius: var(--radius-md);
padding: var(--space-md);
}
.mobile-data-card .card-header {
font-weight: var(--font-semibold);
color: var(--text-color);
margin-bottom: var(--space-xs);
font-size: var(--text-base);
}
.mobile-data-card .card-row {
display: flex;
justify-content: space-between;
align-items: center;
font-size: var(--text-sm);
color: var(--text-color-secondary);
}
.mobile-data-card .card-amount {
font-weight: var(--font-semibold);
color: var(--text-color);
}
.mobile-data-card .card-amount.positive {
color: var(--green-600);
}
.mobile-empty {
text-align: center;
padding: var(--space-xl);
color: var(--text-color-secondary);
}
.mobile-empty i {
font-size: var(--text-3xl);
margin-bottom: var(--space-sm);
display: block;
}
/* ================================================
US-107: Bottom Sheet Styles
================================================ */
.bottom-sheet-title {
font-size: var(--text-lg);
font-weight: var(--font-semibold);
color: var(--text-color);
margin: 0 0 var(--space-md) 0;
}
.bottom-sheet-filters {
display: flex;
flex-direction: column;
gap: var(--space-md);
}
.bottom-sheet-actions {
display: flex;
gap: var(--space-sm);
justify-content: flex-end;
margin-top: var(--space-md);
padding-top: var(--space-md);
border-top: 1px solid var(--surface-border);
}
/* ================================================
US-107: Mobile Sidebar Menu Styles
================================================ */
.sidebar-header {
padding: var(--space-md);
}
.sidebar-title {
font-size: var(--text-xl);
font-weight: var(--font-bold);
color: var(--text-color);
}
.sidebar-menu {
display: flex;
flex-direction: column;
padding: var(--space-sm);
}
.sidebar-item {
display: flex;
align-items: center;
gap: var(--space-md);
padding: var(--space-md);
color: var(--text-color);
text-decoration: none;
border-radius: var(--radius-md);
font-weight: var(--font-medium);
transition: background var(--transition-fast);
}
.sidebar-item:hover {
background: var(--surface-hover);
}
.sidebar-item.active {
background: var(--blue-50);
color: var(--color-primary);
}
.sidebar-item i {
font-size: var(--text-xl);
width: 24px;
text-align: center;
}
/* ================================================
Summary Stats
================================================ */
.summary-stats-inline {
display: flex;
justify-content: flex-end;
margin-bottom: var(--space-md);
}
.stat-item {
display: flex;
align-items: center;
gap: var(--space-sm);
}
.stat-label {
font-size: var(--text-sm);
color: var(--text-color-secondary);
}
.stat-value {
font-size: var(--text-lg);
font-weight: var(--font-bold);
}
.stat-value.plati {
color: var(--red-600);
}
.stat-value.incasari {
color: var(--green-600);
}
/* ================================================
Dark Mode Support
================================================ */
[data-theme="dark"] .mobile-totals-bar .total-value.positive {
color: var(--green-400);
}
[data-theme="dark"] .mobile-totals-bar .total-value.negative {
color: var(--red-400);
}
[data-theme="dark"] .mobile-data-card .card-amount.positive {
color: var(--green-400);
}
[data-theme="dark"] .sidebar-item.active {
background: var(--blue-900);
color: var(--blue-400);
}
[data-theme="dark"] .stat-value.plati {
color: var(--red-400);
}
[data-theme="dark"] .stat-value.incasari {
color: var(--green-400);
}
/* Auto dark mode */
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) .mobile-totals-bar .total-value.positive {
color: var(--green-400);
}
:root:not([data-theme]) .mobile-totals-bar .total-value.negative {
color: var(--red-400);
}
:root:not([data-theme]) .mobile-data-card .card-amount.positive {
color: var(--green-400);
}
:root:not([data-theme]) .sidebar-item.active {
background: var(--blue-900);
color: var(--blue-400);
}
}
/* ================================================
Responsive Design
================================================ */
/* Responsive design */
@media (max-width: 768px) {
.invoices {
padding: 1rem;
padding: var(--space-md);
}
.page-title {
font-size: 2rem;
font-size: var(--text-3xl);
}
.search-col {