feat(ui-fixes-phase6): Complete US-701 - Reparare SpeedDial FAB pe Lista Bonuri
Implemented by Ralph autonomous loop. Iteration: 1 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -133,6 +133,7 @@
|
||||
v-model="showDrawer"
|
||||
:user="authStore.user"
|
||||
:companies-store="companyStore"
|
||||
:period-store="periodStore"
|
||||
@logout="handleLogout"
|
||||
/>
|
||||
|
||||
@@ -1078,7 +1079,7 @@ import { useRouter } from 'vue-router'
|
||||
import { useToast } from 'primevue/usetoast'
|
||||
import { useConfirm } from 'primevue/useconfirm'
|
||||
import { useReceiptsStore } from '@data-entry/stores/receiptsStore'
|
||||
import { useCompanyStore, useAuthStore } from '@data-entry/stores/sharedStores'
|
||||
import { useCompanyStore, useAuthStore, useAccountingPeriodStore } from '@data-entry/stores/sharedStores'
|
||||
import { useBatchProgressStore } from '@data-entry/stores/batchProgressStore'
|
||||
import Menu from 'primevue/menu'
|
||||
import Dialog from 'primevue/dialog'
|
||||
@@ -1096,6 +1097,7 @@ import { exportToExcel, exportToPDF } from '@reports/utils/exportUtils'
|
||||
import BatchGroupHeader from '@data-entry/components/bulk/BatchGroupHeader.vue'
|
||||
import ProcessingStatusCell from '@data-entry/components/bulk/ProcessingStatusCell.vue'
|
||||
import Paginator from 'primevue/paginator'
|
||||
import SpeedDial from 'primevue/speeddial'
|
||||
import { sseService } from '@data-entry/services/sseService'
|
||||
|
||||
const router = useRouter()
|
||||
@@ -1103,6 +1105,7 @@ const toast = useToast()
|
||||
const confirm = useConfirm()
|
||||
const store = useReceiptsStore()
|
||||
const companyStore = useCompanyStore()
|
||||
const periodStore = useAccountingPeriodStore()
|
||||
const batchProgressStore = useBatchProgressStore()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<!-- US-602: TabView for Clienți/Furnizori -->
|
||||
<!-- US-608: Flat content structure (tabs controlled by parent view) -->
|
||||
<div class="maturity-card">
|
||||
<div class="card-header">
|
||||
<h3>Analiză Scadențe</h3>
|
||||
<!-- Period selector (only shown on desktop, mobile uses view header) -->
|
||||
<div class="card-controls">
|
||||
<select
|
||||
v-model="selectedPeriod"
|
||||
@change="handlePeriodChange"
|
||||
@@ -29,123 +29,105 @@
|
||||
<button @click="loadData" class="retry-btn">Încearcă din nou</button>
|
||||
</div>
|
||||
|
||||
<!-- US-602: TabView for Clienți/Furnizori -->
|
||||
<TabView v-else v-model:activeIndex="activeTabIndex" class="maturity-tabs">
|
||||
<!-- Tab Clienți -->
|
||||
<TabPanel>
|
||||
<template #header>
|
||||
<span class="tab-header">
|
||||
<i class="pi pi-users"></i>
|
||||
<span class="tab-title">Clienți</span>
|
||||
<span class="tab-total">{{ formatCurrency(clientsTotal) }}</span>
|
||||
</span>
|
||||
</template>
|
||||
<div class="tab-content">
|
||||
<div class="maturity-list">
|
||||
<div
|
||||
v-for="(client, index) in clientsData"
|
||||
:key="`client-${index}`"
|
||||
class="maturity-item"
|
||||
:class="{
|
||||
overdue: client.daysOverdue > 0,
|
||||
critical: client.daysOverdue > 30,
|
||||
}"
|
||||
>
|
||||
<div class="item-info">
|
||||
<span class="entity-name">{{ client.name }}</span>
|
||||
<span class="due-info">
|
||||
<span v-if="client.daysOverdue > 0" class="overdue-days">
|
||||
Restant {{ client.daysOverdue }} zile
|
||||
</span>
|
||||
<span v-else class="due-date">
|
||||
Scadent în {{ Math.abs(client.daysOverdue) }} zile
|
||||
</span>
|
||||
<!-- US-608: Content based on activeTab prop (no TabView) -->
|
||||
<div v-else class="maturity-content">
|
||||
<!-- Clienți Content -->
|
||||
<div v-if="activeTab === 'clients'" class="tab-content">
|
||||
<div class="maturity-list">
|
||||
<div
|
||||
v-for="(client, index) in clientsData"
|
||||
:key="`client-${index}`"
|
||||
class="maturity-item"
|
||||
:class="{
|
||||
overdue: client.daysOverdue > 0,
|
||||
critical: client.daysOverdue > 30,
|
||||
}"
|
||||
>
|
||||
<div class="item-info">
|
||||
<span class="entity-name">{{ client.name }}</span>
|
||||
<span class="due-info">
|
||||
<span v-if="client.daysOverdue > 0" class="overdue-days">
|
||||
Restant {{ client.daysOverdue }} zile
|
||||
</span>
|
||||
</div>
|
||||
<div class="amount-bar">
|
||||
<div class="bar-container">
|
||||
<div
|
||||
class="bar-fill clients-bar"
|
||||
:style="{
|
||||
width: getBarWidth(client.amount, maxClientAmount) + '%',
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
<span class="amount-value">{{
|
||||
formatCurrency(client.amount)
|
||||
}}</span>
|
||||
</div>
|
||||
<span v-else class="due-date">
|
||||
Scadent în {{ Math.abs(client.daysOverdue) }} zile
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="clientsData.length === 0" class="empty-state">
|
||||
<i class="pi pi-inbox empty-icon"></i>
|
||||
<p>Nu există facturi de încasat pentru această perioadă</p>
|
||||
<div class="amount-bar">
|
||||
<div class="bar-container">
|
||||
<div
|
||||
class="bar-fill clients-bar"
|
||||
:style="{
|
||||
width: getBarWidth(client.amount, maxClientAmount) + '%',
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
<span class="amount-value">{{
|
||||
formatCurrency(client.amount)
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-summary">
|
||||
<span class="summary-label">Total de încasat:</span>
|
||||
<span class="summary-value clients-value">{{ formatCurrency(clientsTotal) }}</span>
|
||||
<div v-if="clientsData.length === 0" class="empty-state">
|
||||
<i class="pi pi-inbox empty-icon"></i>
|
||||
<p>Nu există facturi de încasat pentru această perioadă</p>
|
||||
</div>
|
||||
</div>
|
||||
</TabPanel>
|
||||
<div class="tab-summary">
|
||||
<span class="summary-label">Total de încasat:</span>
|
||||
<span class="summary-value clients-value">{{ formatCurrency(clientsTotal) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab Furnizori -->
|
||||
<TabPanel>
|
||||
<template #header>
|
||||
<span class="tab-header">
|
||||
<i class="pi pi-building"></i>
|
||||
<span class="tab-title">Furnizori</span>
|
||||
<span class="tab-total">{{ formatCurrency(suppliersTotal) }}</span>
|
||||
</span>
|
||||
</template>
|
||||
<div class="tab-content">
|
||||
<div class="maturity-list">
|
||||
<div
|
||||
v-for="(supplier, index) in suppliersData"
|
||||
:key="`supplier-${index}`"
|
||||
class="maturity-item"
|
||||
:class="{
|
||||
overdue: supplier.daysOverdue > 0,
|
||||
critical: supplier.daysOverdue > 30,
|
||||
}"
|
||||
>
|
||||
<div class="item-info">
|
||||
<span class="entity-name">{{ supplier.name }}</span>
|
||||
<span class="due-info">
|
||||
<span v-if="supplier.daysOverdue > 0" class="overdue-days">
|
||||
Restant {{ supplier.daysOverdue }} zile
|
||||
</span>
|
||||
<span v-else class="due-date">
|
||||
Scadent în {{ Math.abs(supplier.daysOverdue) }} zile
|
||||
</span>
|
||||
<!-- Furnizori Content -->
|
||||
<div v-else-if="activeTab === 'suppliers'" class="tab-content">
|
||||
<div class="maturity-list">
|
||||
<div
|
||||
v-for="(supplier, index) in suppliersData"
|
||||
:key="`supplier-${index}`"
|
||||
class="maturity-item"
|
||||
:class="{
|
||||
overdue: supplier.daysOverdue > 0,
|
||||
critical: supplier.daysOverdue > 30,
|
||||
}"
|
||||
>
|
||||
<div class="item-info">
|
||||
<span class="entity-name">{{ supplier.name }}</span>
|
||||
<span class="due-info">
|
||||
<span v-if="supplier.daysOverdue > 0" class="overdue-days">
|
||||
Restant {{ supplier.daysOverdue }} zile
|
||||
</span>
|
||||
</div>
|
||||
<div class="amount-bar">
|
||||
<div class="bar-container">
|
||||
<div
|
||||
class="bar-fill suppliers-bar"
|
||||
:style="{
|
||||
width:
|
||||
getBarWidth(supplier.amount, maxSupplierAmount) + '%',
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
<span class="amount-value">{{
|
||||
formatCurrency(supplier.amount)
|
||||
}}</span>
|
||||
</div>
|
||||
<span v-else class="due-date">
|
||||
Scadent în {{ Math.abs(supplier.daysOverdue) }} zile
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="suppliersData.length === 0" class="empty-state">
|
||||
<i class="pi pi-inbox empty-icon"></i>
|
||||
<p>Nu există facturi de plătit pentru această perioadă</p>
|
||||
<div class="amount-bar">
|
||||
<div class="bar-container">
|
||||
<div
|
||||
class="bar-fill suppliers-bar"
|
||||
:style="{
|
||||
width:
|
||||
getBarWidth(supplier.amount, maxSupplierAmount) + '%',
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
<span class="amount-value">{{
|
||||
formatCurrency(supplier.amount)
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-summary">
|
||||
<span class="summary-label">Total de plătit:</span>
|
||||
<span class="summary-value suppliers-value">{{ formatCurrency(suppliersTotal) }}</span>
|
||||
<div v-if="suppliersData.length === 0" class="empty-state">
|
||||
<i class="pi pi-inbox empty-icon"></i>
|
||||
<p>Nu există facturi de plătit pentru această perioadă</p>
|
||||
</div>
|
||||
</div>
|
||||
</TabPanel>
|
||||
</TabView>
|
||||
<div class="tab-summary">
|
||||
<span class="summary-label">Total de plătit:</span>
|
||||
<span class="summary-value suppliers-value">{{ formatCurrency(suppliersTotal) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Balance Indicator -->
|
||||
<div v-if="!isLoading && !error" class="balance-indicator">
|
||||
@@ -196,54 +178,34 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, watch } from "vue";
|
||||
import TabView from "primevue/tabview";
|
||||
import TabPanel from "primevue/tabpanel";
|
||||
import { useDashboardStore } from "@reports/stores/dashboard";
|
||||
|
||||
// US-602: Tab state storage key
|
||||
const TAB_STORAGE_KEY = "maturity_analysis_active_tab";
|
||||
|
||||
// Props
|
||||
const props = defineProps({
|
||||
companyId: {
|
||||
type: [Number, String],
|
||||
required: true,
|
||||
},
|
||||
// US-602: Allow external tab control (e.g., from route query)
|
||||
initialTab: {
|
||||
type: Number,
|
||||
default: null,
|
||||
// US-608: Tab is now controlled by parent view
|
||||
activeTab: {
|
||||
type: String,
|
||||
default: 'clients',
|
||||
validator: (value) => ['clients', 'suppliers'].includes(value)
|
||||
},
|
||||
});
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits(["periodChanged", "tabChanged"]);
|
||||
const emit = defineEmits(["periodChanged"]);
|
||||
|
||||
// Store
|
||||
const dashboardStore = useDashboardStore();
|
||||
|
||||
// US-602: Initialize tab from prop, localStorage, or default to 0 (Clienți)
|
||||
const getInitialTabIndex = () => {
|
||||
if (props.initialTab !== null) {
|
||||
return props.initialTab;
|
||||
}
|
||||
const stored = localStorage.getItem(TAB_STORAGE_KEY);
|
||||
return stored !== null ? parseInt(stored, 10) : 0;
|
||||
};
|
||||
|
||||
// Reactive state
|
||||
const activeTabIndex = ref(getInitialTabIndex());
|
||||
const selectedPeriod = ref("1m");
|
||||
const isLoading = ref(false);
|
||||
const error = ref(null);
|
||||
const lastUpdated = ref(null);
|
||||
|
||||
// US-602: Watch tab changes and persist to localStorage
|
||||
watch(activeTabIndex, (newIndex) => {
|
||||
localStorage.setItem(TAB_STORAGE_KEY, newIndex.toString());
|
||||
emit("tabChanged", newIndex);
|
||||
});
|
||||
|
||||
// Mock data structure - in production this would come from API
|
||||
const maturityData = ref({
|
||||
clients: [],
|
||||
@@ -384,48 +346,31 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Base Card Styles */
|
||||
/* US-608: Flat content structure (no card wrapper) */
|
||||
.maturity-card {
|
||||
background: var(--color-bg);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--card-radius, 8px);
|
||||
padding: 0;
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition: all var(--transition-fast, 0.3s ease);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.maturity-card:hover {
|
||||
box-shadow: var(--shadow-md);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* Card Header */
|
||||
.card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: var(--space-lg, 1rem);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.card-header h3 {
|
||||
margin: 0;
|
||||
font-size: var(--text-lg, 1.125rem);
|
||||
font-weight: var(--font-semibold, 600);
|
||||
color: var(--color-text);
|
||||
/* Card Controls (period selector) */
|
||||
.card-controls {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: var(--space-md);
|
||||
background: var(--surface-card);
|
||||
border-radius: var(--radius-md);
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
|
||||
.period-selector {
|
||||
padding: 0.5rem 0.75rem;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm, 4px);
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
font-size: var(--text-sm, 0.875rem);
|
||||
padding: var(--space-sm) var(--space-md);
|
||||
border: 1px solid var(--surface-border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--surface-card);
|
||||
color: var(--text-color);
|
||||
font-size: var(--text-sm);
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s ease;
|
||||
transition: border-color var(--transition-fast);
|
||||
}
|
||||
|
||||
.period-selector:hover {
|
||||
@@ -437,6 +382,13 @@ onMounted(() => {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Maturity Content Container */
|
||||
.maturity-content {
|
||||
background: var(--surface-card);
|
||||
border-radius: var(--radius-md);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Loading and Error States */
|
||||
.loading-state,
|
||||
.error-state {
|
||||
@@ -478,38 +430,6 @@ onMounted(() => {
|
||||
background: var(--color-primary-dark);
|
||||
}
|
||||
|
||||
/* US-602: TabView Styles */
|
||||
.maturity-tabs {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* Tab header styles */
|
||||
.tab-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
padding: var(--space-xs) 0;
|
||||
}
|
||||
|
||||
.tab-header i {
|
||||
font-size: var(--text-base);
|
||||
color: var(--text-color-secondary);
|
||||
}
|
||||
|
||||
.tab-title {
|
||||
font-weight: var(--font-medium);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.tab-total {
|
||||
font-size: var(--text-xs);
|
||||
font-weight: var(--font-bold);
|
||||
padding: var(--space-xs) var(--space-sm);
|
||||
background: var(--surface-hover);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
/* Tab content */
|
||||
.tab-content {
|
||||
padding: var(--space-md);
|
||||
@@ -820,37 +740,21 @@ onMounted(() => {
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 768px) {
|
||||
.card-header {
|
||||
flex-direction: column;
|
||||
gap: var(--space-sm);
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.card-header h3 {
|
||||
text-align: center;
|
||||
font-size: var(--text-base);
|
||||
.card-controls {
|
||||
justify-content: center;
|
||||
padding: var(--space-sm);
|
||||
}
|
||||
|
||||
.period-selector {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* US-602: Mobile tab adjustments */
|
||||
/* US-608: Mobile tab adjustments */
|
||||
.tab-content {
|
||||
padding: var(--space-sm);
|
||||
min-height: 250px;
|
||||
}
|
||||
|
||||
.tab-header {
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-xs);
|
||||
}
|
||||
|
||||
.tab-total {
|
||||
font-size: var(--text-xs);
|
||||
padding: 2px var(--space-xs);
|
||||
}
|
||||
|
||||
.tab-summary {
|
||||
padding: var(--space-sm);
|
||||
flex-direction: column;
|
||||
@@ -876,22 +780,13 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.maturity-card {
|
||||
margin: 0 calc(-1 * var(--space-sm));
|
||||
}
|
||||
|
||||
.card-header,
|
||||
.balance-indicator,
|
||||
.card-footer {
|
||||
padding: var(--space-md);
|
||||
}
|
||||
|
||||
.maturity-list {
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
.tab-title {
|
||||
font-size: var(--text-sm);
|
||||
.balance-indicator,
|
||||
.card-footer {
|
||||
padding: var(--space-md);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
<template>
|
||||
<div class="cache-stats-view" :class="{ 'mobile-layout': isMobile }">
|
||||
<!-- US-111: Mobile Material Design Top Bar -->
|
||||
<!-- US-608: Mobile Material Design Top Bar with Back Button -->
|
||||
<MobileTopBar
|
||||
v-if="isMobile"
|
||||
title="Statistici Cache"
|
||||
:show-menu="true"
|
||||
:show-back="true"
|
||||
:actions="mobileTopBarActions"
|
||||
@menu-click="showDrawer = true"
|
||||
@back-click="router.push('/settings')"
|
||||
@action-click="handleTopBarAction"
|
||||
/>
|
||||
|
||||
<!-- Mobile Drawer Menu (replaces old Sidebar) -->
|
||||
<MobileDrawerMenu
|
||||
v-model="showDrawer"
|
||||
:user="authStore.user"
|
||||
:companies-store="companyStore"
|
||||
@logout="handleLogout"
|
||||
/>
|
||||
|
||||
<!-- Desktop Header -->
|
||||
<div class="stats-header" v-if="!isMobile">
|
||||
<h1>Cache Statistics</h1>
|
||||
@@ -206,7 +198,7 @@
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted } from "vue";
|
||||
import { useCacheStore } from "@reports/stores/cacheStore";
|
||||
import { useCompanyStore, useAuthStore } from "@reports/stores/sharedStores";
|
||||
import { useCompanyStore, useAuthStore, useAccountingPeriodStore } from "@reports/stores/sharedStores";
|
||||
import { useToast } from "primevue/usetoast";
|
||||
import Button from "primevue/button";
|
||||
import Card from "primevue/card";
|
||||
@@ -218,15 +210,15 @@ import InputSwitch from "primevue/inputswitch";
|
||||
import Dialog from "primevue/dialog";
|
||||
import RadioButton from "primevue/radiobutton";
|
||||
import Message from "primevue/message";
|
||||
// US-111: Mobile Material Design components
|
||||
// US-111/US-608: Mobile Material Design components
|
||||
import MobileTopBar from "@shared/components/mobile/MobileTopBar.vue";
|
||||
import MobileBottomNav from "@shared/components/mobile/MobileBottomNav.vue";
|
||||
import MobileDrawerMenu from "@shared/components/mobile/MobileDrawerMenu.vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
const router = useRouter();
|
||||
const cacheStore = useCacheStore();
|
||||
const companyStore = useCompanyStore();
|
||||
const periodStore = useAccountingPeriodStore();
|
||||
const toast = useToast();
|
||||
const authStore = useAuthStore();
|
||||
|
||||
@@ -238,15 +230,8 @@ const userCacheEnabled = ref(true);
|
||||
const showClearDialog = ref(false);
|
||||
const clearScope = ref("current");
|
||||
|
||||
// US-111: Mobile state
|
||||
// US-111/US-608: Mobile state
|
||||
const isMobile = ref(window.innerWidth < 768);
|
||||
const showDrawer = ref(false);
|
||||
|
||||
// Handle logout from drawer menu
|
||||
const handleLogout = async () => {
|
||||
await authStore.logout();
|
||||
router.push('/login');
|
||||
};
|
||||
|
||||
// US-111: Mobile TopBar actions (refresh only for cache stats)
|
||||
const mobileTopBarActions = computed(() => [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<!-- US-513: Simplified Mobile Top Bar - no filters/export since no detailed data -->
|
||||
<!-- US-608: Mobile Top Bar with Back Button -->
|
||||
<MobileTopBar
|
||||
v-if="isMobile"
|
||||
title="Scadențe"
|
||||
@@ -7,12 +7,55 @@
|
||||
@back-click="goBack"
|
||||
/>
|
||||
|
||||
<main class="main-content" :class="{ 'mobile-layout': isMobile }">
|
||||
<!-- US-608: Mobile Tabs (sticky below MobileTopBar) - like DetailedInvoicesView -->
|
||||
<div v-if="isMobile && companyStore.selectedCompany" class="mobile-tabs-container">
|
||||
<div class="mobile-tabs">
|
||||
<button
|
||||
class="mobile-tab"
|
||||
:class="{ active: activeTab === 'clients' }"
|
||||
@click="switchTab('clients')"
|
||||
>
|
||||
<span class="tab-label">Clienți</span>
|
||||
</button>
|
||||
<button
|
||||
class="mobile-tab"
|
||||
:class="{ active: activeTab === 'suppliers' }"
|
||||
@click="switchTab('suppliers')"
|
||||
>
|
||||
<span class="tab-label">Furnizori</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<main class="main-content" :class="{ 'mobile-layout': isMobile, 'has-tabs': isMobile && companyStore.selectedCompany }">
|
||||
<div class="app-container">
|
||||
<!-- Page Header - only on desktop -->
|
||||
<div v-if="!isMobile" class="page-header">
|
||||
<h1 class="page-title">Analiză Scadențe</h1>
|
||||
<p class="page-subtitle">Analiza scadențelor clienți și furnizori</p>
|
||||
<div class="header-top">
|
||||
<div>
|
||||
<h1 class="page-title">Analiză Scadențe</h1>
|
||||
<p class="page-subtitle">Analiza scadențelor clienți și furnizori</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- US-608: Desktop Tabs (in header) -->
|
||||
<div v-if="companyStore.selectedCompany" class="desktop-tabs">
|
||||
<button
|
||||
class="desktop-tab"
|
||||
:class="{ active: activeTab === 'clients' }"
|
||||
@click="switchTab('clients')"
|
||||
>
|
||||
<i class="pi pi-users"></i>
|
||||
<span>Clienți</span>
|
||||
</button>
|
||||
<button
|
||||
class="desktop-tab"
|
||||
:class="{ active: activeTab === 'suppliers' }"
|
||||
@click="switchTab('suppliers')"
|
||||
>
|
||||
<i class="pi pi-building"></i>
|
||||
<span>Furnizori</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading state when no company selected -->
|
||||
@@ -30,14 +73,13 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Main content - MaturityAnalysisCard (US-513: doar analiza scadențelor, fără facturi detaliate) -->
|
||||
<!-- US-602: Now with TabView for Clienți/Furnizori -->
|
||||
<div v-else class="maturity-container">
|
||||
<!-- US-608: Flat content structure - no card wrapper -->
|
||||
<div v-else class="maturity-content">
|
||||
<MaturityAnalysisCard
|
||||
ref="maturityCardRef"
|
||||
:companyId="companyStore.selectedCompany?.id_firma"
|
||||
:activeTab="activeTab"
|
||||
@periodChanged="handlePeriodChange"
|
||||
@tabChanged="handleTabChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -49,7 +91,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import Button from 'primevue/button'
|
||||
import MobileTopBar from '@shared/components/mobile/MobileTopBar.vue'
|
||||
import MobileBottomNav from '@shared/components/mobile/MobileBottomNav.vue'
|
||||
@@ -57,6 +99,7 @@ import MaturityAnalysisCard from '@reports/components/dashboard/cards/MaturityAn
|
||||
import { useCompanyStore } from '@reports/stores/sharedStores'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const companyStore = useCompanyStore()
|
||||
|
||||
// Detectare mobile - reactive with resize listener
|
||||
@@ -66,6 +109,30 @@ const isMobile = computed(() => windowWidth.value < 768)
|
||||
// Ref to MaturityAnalysisCard
|
||||
const maturityCardRef = ref(null)
|
||||
|
||||
// US-608: Tab state (persisted via URL query)
|
||||
const TAB_STORAGE_KEY = 'maturity_analysis_active_tab'
|
||||
|
||||
const getInitialTab = () => {
|
||||
// Check URL query first
|
||||
if (route.query.tab === 'suppliers') return 'suppliers'
|
||||
// Then localStorage
|
||||
const stored = localStorage.getItem(TAB_STORAGE_KEY)
|
||||
if (stored === 'suppliers') return 'suppliers'
|
||||
return 'clients'
|
||||
}
|
||||
|
||||
const activeTab = ref(getInitialTab())
|
||||
|
||||
// US-608: Switch tab and update URL
|
||||
const switchTab = (tab) => {
|
||||
activeTab.value = tab
|
||||
localStorage.setItem(TAB_STORAGE_KEY, tab)
|
||||
// Update URL without full navigation
|
||||
router.replace({
|
||||
query: tab === 'suppliers' ? { tab: 'suppliers' } : {}
|
||||
})
|
||||
}
|
||||
|
||||
// Handle window resize for mobile detection
|
||||
const handleResize = () => {
|
||||
windowWidth.value = window.innerWidth
|
||||
@@ -81,11 +148,6 @@ const handlePeriodChange = (period) => {
|
||||
console.log('Maturity period changed:', period)
|
||||
}
|
||||
|
||||
// US-602: Handle tab change
|
||||
const handleTabChange = (tabIndex) => {
|
||||
console.log('Tab changed:', tabIndex === 0 ? 'Clienți' : 'Furnizori')
|
||||
}
|
||||
|
||||
// Lifecycle
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', handleResize)
|
||||
@@ -103,6 +165,11 @@ onUnmounted(() => {
|
||||
padding-bottom: 56px;
|
||||
}
|
||||
|
||||
/* US-608: Extra padding when tabs are visible on mobile */
|
||||
.main-content.mobile-layout.has-tabs {
|
||||
padding-top: calc(56px + 48px);
|
||||
}
|
||||
|
||||
/* App container */
|
||||
.app-container {
|
||||
max-width: 1400px;
|
||||
@@ -117,11 +184,17 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
/* Page Header - Desktop only */
|
||||
/* US-514: Reduced from var(--space-xl) to var(--space-md) for less excessive top spacing */
|
||||
.page-header {
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
|
||||
.header-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: var(--text-3xl);
|
||||
font-weight: var(--font-bold);
|
||||
@@ -135,8 +208,102 @@ onUnmounted(() => {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Maturity container */
|
||||
.maturity-container {
|
||||
/* ================================================
|
||||
US-608: Mobile Tabs (sticky below MobileTopBar)
|
||||
Copied from DetailedInvoicesView for consistency
|
||||
================================================ */
|
||||
|
||||
.mobile-tabs-container {
|
||||
position: fixed;
|
||||
top: 56px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: var(--z-sticky, 100);
|
||||
background: var(--surface-card);
|
||||
border-bottom: 1px solid var(--surface-border);
|
||||
}
|
||||
|
||||
.mobile-tabs {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mobile-tab {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--space-md);
|
||||
min-height: 48px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
color: var(--text-color-secondary);
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-medium);
|
||||
}
|
||||
|
||||
.mobile-tab.active {
|
||||
color: var(--color-primary);
|
||||
border-bottom-color: var(--color-primary);
|
||||
font-weight: var(--font-semibold);
|
||||
}
|
||||
|
||||
.mobile-tab:hover:not(.active) {
|
||||
background: var(--surface-hover);
|
||||
}
|
||||
|
||||
.tab-label {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
/* ================================================
|
||||
US-608: Desktop Tabs (in page-header)
|
||||
================================================ */
|
||||
|
||||
.desktop-tabs {
|
||||
display: flex;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.desktop-tab {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
padding: var(--space-sm) var(--space-lg);
|
||||
background: var(--surface-hover);
|
||||
border: 1px solid var(--surface-border);
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
color: var(--text-color-secondary);
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-medium);
|
||||
}
|
||||
|
||||
.desktop-tab:hover:not(.active) {
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.desktop-tab.active {
|
||||
background: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-text-inverse, white);
|
||||
}
|
||||
|
||||
.desktop-tab i {
|
||||
font-size: var(--text-base);
|
||||
}
|
||||
|
||||
/* ================================================
|
||||
US-608: Flat content (no card wrapper)
|
||||
================================================ */
|
||||
|
||||
.maturity-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-lg);
|
||||
@@ -180,4 +347,40 @@ onUnmounted(() => {
|
||||
.empty-action {
|
||||
margin-top: var(--space-md);
|
||||
}
|
||||
|
||||
/* ================================================
|
||||
Dark Mode Support
|
||||
================================================ */
|
||||
|
||||
[data-theme="dark"] .mobile-tabs-container {
|
||||
background: var(--surface-card);
|
||||
border-bottom-color: var(--surface-border);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .mobile-tab.active {
|
||||
color: var(--blue-400);
|
||||
border-bottom-color: var(--blue-400);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .desktop-tab.active {
|
||||
background: var(--blue-600);
|
||||
border-color: var(--blue-600);
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme]) .mobile-tabs-container {
|
||||
background: var(--surface-card);
|
||||
border-bottom-color: var(--surface-border);
|
||||
}
|
||||
|
||||
:root:not([data-theme]) .mobile-tab.active {
|
||||
color: var(--blue-400);
|
||||
border-bottom-color: var(--blue-400);
|
||||
}
|
||||
|
||||
:root:not([data-theme]) .desktop-tab.active {
|
||||
background: var(--blue-600);
|
||||
border-color: var(--blue-600);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,22 +1,15 @@
|
||||
<template>
|
||||
<div class="server-logs-view" :class="{ 'mobile-layout': isMobile }">
|
||||
<!-- US-110: Mobile Material Design Top Bar -->
|
||||
<!-- US-608: Mobile Material Design Top Bar with Back Button -->
|
||||
<MobileTopBar
|
||||
v-if="isMobile"
|
||||
title="Loguri Server"
|
||||
:show-menu="true"
|
||||
:show-back="true"
|
||||
:actions="mobileTopBarActions"
|
||||
@menu-click="showDrawer = true"
|
||||
@back-click="router.push('/settings')"
|
||||
@action-click="handleTopBarAction"
|
||||
/>
|
||||
|
||||
<!-- Mobile Drawer Menu (replaces old Sidebar) -->
|
||||
<MobileDrawerMenu
|
||||
v-model="showDrawer"
|
||||
:user="authStore.user"
|
||||
@logout="handleLogout"
|
||||
/>
|
||||
|
||||
<!-- Desktop Header -->
|
||||
<div class="stats-header" v-if="!isMobile">
|
||||
<h1>
|
||||
@@ -140,11 +133,9 @@ import Tag from 'primevue/tag'
|
||||
import axios from 'axios'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
// US-110: Mobile Material Design components
|
||||
// US-110/US-608: Mobile Material Design components
|
||||
import MobileTopBar from '@shared/components/mobile/MobileTopBar.vue'
|
||||
import MobileBottomNav from '@shared/components/mobile/MobileBottomNav.vue'
|
||||
import MobileDrawerMenu from '@shared/components/mobile/MobileDrawerMenu.vue'
|
||||
import { useAuthStore } from '@reports/stores/sharedStores'
|
||||
|
||||
// System API - endpoint separat de reports
|
||||
const systemApi = axios.create({
|
||||
@@ -162,7 +153,6 @@ systemApi.interceptors.request.use((config) => {
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
// State
|
||||
const logs = ref([])
|
||||
@@ -182,15 +172,8 @@ const debugInfo = ref({
|
||||
|
||||
let refreshTimer = null
|
||||
|
||||
// US-110: Mobile state
|
||||
// US-110/US-608: Mobile state
|
||||
const isMobile = ref(window.innerWidth < 768)
|
||||
const showDrawer = ref(false)
|
||||
|
||||
// Handle logout from drawer menu
|
||||
const handleLogout = async () => {
|
||||
await authStore.logout()
|
||||
router.push('/login')
|
||||
}
|
||||
|
||||
// US-110: Mobile TopBar actions (refresh, export)
|
||||
const mobileTopBarActions = computed(() => [
|
||||
|
||||
@@ -12,111 +12,85 @@
|
||||
</div>
|
||||
|
||||
<!-- Company & Period Selection (below header, above navigation) -->
|
||||
<!-- US-605: Collapsible sections with localStorage persistence -->
|
||||
<!-- US-608: Direct dropdowns (no collapsible sections) -->
|
||||
<div v-if="companiesStore" class="drawer-selectors">
|
||||
<!-- Company Selector (Collapsible) -->
|
||||
<div class="selector-group" :class="{ 'is-collapsed': companyCollapsed }">
|
||||
<!-- Collapsible Header -->
|
||||
<!-- Company Selector (Direct Dropdown) -->
|
||||
<div class="selector-group">
|
||||
<label class="selector-label">Firma</label>
|
||||
<button
|
||||
class="collapsible-header"
|
||||
@click="toggleCompanyCollapsed"
|
||||
:aria-expanded="!companyCollapsed"
|
||||
class="selector-trigger"
|
||||
@click="toggleCompanyDropdown"
|
||||
:aria-expanded="companyDropdownOpen"
|
||||
>
|
||||
<span class="collapsible-label">Firma</span>
|
||||
<span v-if="companyCollapsed" class="collapsible-value">{{ selectedCompanyName }}</span>
|
||||
<i class="pi" :class="companyCollapsed ? 'pi-chevron-down' : 'pi-chevron-up'"></i>
|
||||
<div class="selector-value">
|
||||
<span class="selector-main">{{ selectedCompanyName }}</span>
|
||||
<span v-if="selectedCompanyCode" class="selector-sub">{{ selectedCompanyCode }}</span>
|
||||
</div>
|
||||
<i class="pi pi-chevron-down" :class="{ 'rotate-180': companyDropdownOpen }"></i>
|
||||
</button>
|
||||
|
||||
<!-- Expanded Content -->
|
||||
<div v-if="!companyCollapsed" class="collapsible-content">
|
||||
<button
|
||||
class="selector-trigger"
|
||||
@click="toggleCompanyDropdown"
|
||||
:aria-expanded="companyDropdownOpen"
|
||||
>
|
||||
<div class="selector-value">
|
||||
<span class="selector-main">{{ selectedCompanyName }}</span>
|
||||
<span v-if="selectedCompanyCode" class="selector-sub">{{ selectedCompanyCode }}</span>
|
||||
</div>
|
||||
<i class="pi pi-chevron-down" :class="{ 'rotate-180': companyDropdownOpen }"></i>
|
||||
</button>
|
||||
<!-- Company Dropdown Panel -->
|
||||
<div v-if="companyDropdownOpen" class="selector-panel">
|
||||
<div class="selector-search">
|
||||
<i class="pi pi-search"></i>
|
||||
<input
|
||||
ref="companySearchInput"
|
||||
type="text"
|
||||
v-model="companySearchQuery"
|
||||
placeholder="Caută firmă..."
|
||||
class="selector-search-input"
|
||||
/>
|
||||
</div>
|
||||
<div class="selector-list">
|
||||
<div
|
||||
v-for="company in filteredCompanies"
|
||||
:key="company.id_firma"
|
||||
class="selector-item"
|
||||
:class="{ active: company.id_firma === companiesStore.selectedCompany?.id_firma }"
|
||||
@click="selectCompany(company)"
|
||||
>
|
||||
<div class="selector-item-content">
|
||||
<span class="selector-item-name">{{ company.name }}</span>
|
||||
<span v-if="company.fiscal_code" class="selector-item-sub">CUI: {{ company.fiscal_code }}</span>
|
||||
</div>
|
||||
<i v-if="company.id_firma === companiesStore.selectedCompany?.id_firma" class="pi pi-check"></i>
|
||||
</div>
|
||||
<div v-if="filteredCompanies.length === 0" class="selector-empty">
|
||||
<i class="pi pi-info-circle"></i>
|
||||
<span>Nu s-au găsit firme</span>
|
||||
<!-- Company Dropdown Panel -->
|
||||
<div v-if="companyDropdownOpen" class="selector-panel">
|
||||
<div class="selector-search">
|
||||
<i class="pi pi-search"></i>
|
||||
<input
|
||||
ref="companySearchInput"
|
||||
type="text"
|
||||
v-model="companySearchQuery"
|
||||
placeholder="Caută firmă..."
|
||||
class="selector-search-input"
|
||||
/>
|
||||
</div>
|
||||
<div class="selector-list">
|
||||
<div
|
||||
v-for="company in filteredCompanies"
|
||||
:key="company.id_firma"
|
||||
class="selector-item"
|
||||
:class="{ active: company.id_firma === companiesStore.selectedCompany?.id_firma }"
|
||||
@click="selectCompany(company)"
|
||||
>
|
||||
<div class="selector-item-content">
|
||||
<span class="selector-item-name">{{ company.name }}</span>
|
||||
<span v-if="company.fiscal_code" class="selector-item-sub">CUI: {{ company.fiscal_code }}</span>
|
||||
</div>
|
||||
<i v-if="company.id_firma === companiesStore.selectedCompany?.id_firma" class="pi pi-check"></i>
|
||||
</div>
|
||||
<div v-if="filteredCompanies.length === 0" class="selector-empty">
|
||||
<i class="pi pi-info-circle"></i>
|
||||
<span>Nu s-au găsit firme</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Period Selector (Collapsible) -->
|
||||
<div v-if="periodStore && companiesStore.selectedCompany" class="selector-group" :class="{ 'is-collapsed': periodCollapsed }">
|
||||
<!-- Collapsible Header -->
|
||||
<!-- Period Selector (Direct Dropdown) -->
|
||||
<div v-if="periodStore && companiesStore.selectedCompany" class="selector-group">
|
||||
<label class="selector-label">Perioada</label>
|
||||
<button
|
||||
class="collapsible-header"
|
||||
@click="togglePeriodCollapsed"
|
||||
:aria-expanded="!periodCollapsed"
|
||||
class="selector-trigger"
|
||||
@click="togglePeriodDropdown"
|
||||
:aria-expanded="periodDropdownOpen"
|
||||
>
|
||||
<span class="collapsible-label">Perioada</span>
|
||||
<span v-if="periodCollapsed" class="collapsible-value">{{ selectedPeriodDisplay }}</span>
|
||||
<i class="pi" :class="periodCollapsed ? 'pi-chevron-down' : 'pi-chevron-up'"></i>
|
||||
<div class="selector-value">
|
||||
<span class="selector-main">{{ selectedPeriodDisplay }}</span>
|
||||
</div>
|
||||
<i class="pi pi-chevron-down" :class="{ 'rotate-180': periodDropdownOpen }"></i>
|
||||
</button>
|
||||
|
||||
<!-- Expanded Content -->
|
||||
<div v-if="!periodCollapsed" class="collapsible-content">
|
||||
<button
|
||||
class="selector-trigger"
|
||||
@click="togglePeriodDropdown"
|
||||
:aria-expanded="periodDropdownOpen"
|
||||
>
|
||||
<div class="selector-value">
|
||||
<span class="selector-main">{{ selectedPeriodDisplay }}</span>
|
||||
<!-- Period Dropdown Panel -->
|
||||
<div v-if="periodDropdownOpen" class="selector-panel">
|
||||
<div class="selector-list">
|
||||
<div
|
||||
v-for="period in availablePeriods"
|
||||
:key="`${period.an}-${period.luna}`"
|
||||
class="selector-item"
|
||||
:class="{ active: isPeriodSelected(period) }"
|
||||
@click="selectPeriod(period)"
|
||||
>
|
||||
<span class="selector-item-name">{{ period.display_name }}</span>
|
||||
<i v-if="isPeriodSelected(period)" class="pi pi-check"></i>
|
||||
</div>
|
||||
<i class="pi pi-chevron-down" :class="{ 'rotate-180': periodDropdownOpen }"></i>
|
||||
</button>
|
||||
<!-- Period Dropdown Panel -->
|
||||
<div v-if="periodDropdownOpen" class="selector-panel">
|
||||
<div class="selector-list">
|
||||
<div
|
||||
v-for="period in availablePeriods"
|
||||
:key="`${period.an}-${period.luna}`"
|
||||
class="selector-item"
|
||||
:class="{ active: isPeriodSelected(period) }"
|
||||
@click="selectPeriod(period)"
|
||||
>
|
||||
<span class="selector-item-name">{{ period.display_name }}</span>
|
||||
<i v-if="isPeriodSelected(period)" class="pi pi-check"></i>
|
||||
</div>
|
||||
<div v-if="availablePeriods.length === 0" class="selector-empty">
|
||||
<i class="pi pi-info-circle"></i>
|
||||
<span>Nu sunt perioade disponibile</span>
|
||||
</div>
|
||||
<div v-if="availablePeriods.length === 0" class="selector-empty">
|
||||
<i class="pi pi-info-circle"></i>
|
||||
<span>Nu sunt perioade disponibile</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -252,8 +226,8 @@ import { computed, ref, watch, nextTick, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
/**
|
||||
* MobileDrawerMenu - Material Design 3 inspired navigation drawer for mobile (v3)
|
||||
* US-605: Added collapsible Firma/Perioada sections
|
||||
* MobileDrawerMenu - Material Design 3 inspired navigation drawer for mobile (v4)
|
||||
* US-608: Direct dropdown selectors (removed collapsible sections)
|
||||
*
|
||||
* Props:
|
||||
* - modelValue (v-model): Controls visibility of the drawer
|
||||
@@ -271,15 +245,16 @@ import { useRoute, useRouter } from 'vue-router'
|
||||
* Features:
|
||||
* - Slide-in animation from left
|
||||
* - Header with ROA2WEB logo
|
||||
* - Company & Period selectors (below header, like desktop)
|
||||
* - Company & Period selectors as direct dropdowns (1-tap interaction)
|
||||
* - Navigation organized into 4 category sections:
|
||||
* - PRINCIPALE: Dashboard, Bonuri
|
||||
* - RAPOARTE: Facturi, Balanță, Casă, Bancă (US-519: separate pages)
|
||||
* - RAPOARTE: Facturi, Balanță, Casă, Bancă
|
||||
* - ANALIZE: Scadențe, Facturi Detaliate
|
||||
* - ADMINISTRARE: Setări
|
||||
* - Visual separators between sections
|
||||
* - Active state highlighting based on current route
|
||||
* - Profile section with user name and logout button (footer)
|
||||
* - Profile section with user name, logout button, and theme toggle
|
||||
* - Theme toggle cycles through: Auto → Light → Dark
|
||||
* - Close on tap outside or on link click
|
||||
* - Full dark mode support
|
||||
* - Teleported to body to avoid z-index issues
|
||||
@@ -341,55 +316,7 @@ const companySearchInput = ref(null)
|
||||
// Period selector state
|
||||
const periodDropdownOpen = ref(false)
|
||||
|
||||
// Collapsible section state (US-605)
|
||||
// Default to collapsed, persisted in localStorage
|
||||
const COLLAPSED_STORAGE_KEY = 'mobile-drawer-sections-collapsed'
|
||||
|
||||
const loadCollapsedState = () => {
|
||||
try {
|
||||
const saved = localStorage.getItem(COLLAPSED_STORAGE_KEY)
|
||||
if (saved) {
|
||||
return JSON.parse(saved)
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore parse errors
|
||||
}
|
||||
// Default: both sections collapsed
|
||||
return { company: true, period: true }
|
||||
}
|
||||
|
||||
const savedCollapsedState = loadCollapsedState()
|
||||
const companyCollapsed = ref(savedCollapsedState.company)
|
||||
const periodCollapsed = ref(savedCollapsedState.period)
|
||||
|
||||
const saveCollapsedState = () => {
|
||||
try {
|
||||
localStorage.setItem(COLLAPSED_STORAGE_KEY, JSON.stringify({
|
||||
company: companyCollapsed.value,
|
||||
period: periodCollapsed.value
|
||||
}))
|
||||
} catch (e) {
|
||||
// Ignore storage errors
|
||||
}
|
||||
}
|
||||
|
||||
const toggleCompanyCollapsed = () => {
|
||||
companyCollapsed.value = !companyCollapsed.value
|
||||
// Close dropdown if collapsing
|
||||
if (companyCollapsed.value) {
|
||||
companyDropdownOpen.value = false
|
||||
}
|
||||
saveCollapsedState()
|
||||
}
|
||||
|
||||
const togglePeriodCollapsed = () => {
|
||||
periodCollapsed.value = !periodCollapsed.value
|
||||
// Close dropdown if collapsing
|
||||
if (periodCollapsed.value) {
|
||||
periodDropdownOpen.value = false
|
||||
}
|
||||
saveCollapsedState()
|
||||
}
|
||||
// US-608: Removed collapsible state management - using direct dropdowns now
|
||||
|
||||
// Computed properties for company selector
|
||||
const selectedCompanyName = computed(() => {
|
||||
@@ -855,67 +782,9 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
/* ================================================
|
||||
Collapsible Sections (US-605)
|
||||
US-608: Collapsible styles removed - using direct dropdowns
|
||||
================================================ */
|
||||
|
||||
.collapsible-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: var(--space-sm) var(--space-md);
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
min-height: 44px;
|
||||
gap: var(--space-sm);
|
||||
transition: background var(--transition-fast);
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.collapsible-header:hover {
|
||||
background: var(--surface-hover);
|
||||
}
|
||||
|
||||
.collapsible-header:active {
|
||||
background: var(--surface-hover);
|
||||
}
|
||||
|
||||
.collapsible-label {
|
||||
font-size: var(--text-xs);
|
||||
font-weight: var(--font-medium);
|
||||
color: var(--text-color-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.collapsible-value {
|
||||
flex: 1;
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-medium);
|
||||
color: var(--text-color);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.collapsible-header .pi {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-color-secondary);
|
||||
flex-shrink: 0;
|
||||
transition: transform var(--transition-fast);
|
||||
}
|
||||
|
||||
.collapsible-content {
|
||||
padding-top: var(--space-xs);
|
||||
}
|
||||
|
||||
/* When collapsed, the selector-group has minimal padding */
|
||||
.selector-group.is-collapsed {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
/* ================================================
|
||||
Navigation Sections Container
|
||||
US-606: No longer independently scrollable, part of unified scroll
|
||||
@@ -1022,9 +891,11 @@ onMounted(() => {
|
||||
gap: var(--space-xs);
|
||||
}
|
||||
|
||||
/* US-607: Compact variant - reduced spacing */
|
||||
/* US-607/US-608: Compact variant - reduced spacing but with MobileBottomNav clearance */
|
||||
.drawer-profile--compact {
|
||||
padding: var(--space-sm) var(--space-md);
|
||||
/* US-608: Ensure theme toggle is visible above MobileBottomNav (56px) */
|
||||
padding-bottom: calc(56px + var(--space-md));
|
||||
gap: var(--space-xs);
|
||||
}
|
||||
|
||||
@@ -1279,23 +1150,6 @@ onMounted(() => {
|
||||
color: var(--text-color-secondary);
|
||||
}
|
||||
|
||||
/* Dark mode: Collapsible Sections (US-605) */
|
||||
[data-theme="dark"] .collapsible-header:hover {
|
||||
background: var(--surface-hover);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .collapsible-label {
|
||||
color: var(--text-color-secondary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .collapsible-value {
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .collapsible-header .pi {
|
||||
color: var(--text-color-secondary);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .drawer-link {
|
||||
color: var(--text-color);
|
||||
}
|
||||
@@ -1479,23 +1333,6 @@ onMounted(() => {
|
||||
color: var(--text-color-secondary);
|
||||
}
|
||||
|
||||
/* Auto dark mode: Collapsible Sections (US-605) */
|
||||
:root:not([data-theme]) .collapsible-header:hover {
|
||||
background: var(--surface-hover);
|
||||
}
|
||||
|
||||
:root:not([data-theme]) .collapsible-label {
|
||||
color: var(--text-color-secondary);
|
||||
}
|
||||
|
||||
:root:not([data-theme]) .collapsible-value {
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
:root:not([data-theme]) .collapsible-header .pi {
|
||||
color: var(--text-color-secondary);
|
||||
}
|
||||
|
||||
:root:not([data-theme]) .drawer-link {
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user