feat(ui-fixes-phase6): Complete US-605 - Companie/Perioadă Colapsabile în Meniul Mobil

Implemented by Ralph autonomous loop.
Iteration: 6

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-01-13 16:23:39 +00:00
parent 0737f0626b
commit 36f798eb34
3 changed files with 246 additions and 66 deletions

View File

@@ -116,8 +116,8 @@
"npm run typecheck passes",
"Verify in browser: Tap on Firma/Perioada toggles expansion"
],
"passes": false,
"notes": "Modifică MobileDrawerMenu.vue, adaugă state pentru collapsed și persistență localStorage"
"passes": true,
"notes": "Completed in iteration 6"
},
{
"id": "US-606",

View File

@@ -272,3 +272,9 @@ PRD: tasks/prd-ui-fixes-phase6.md
[2026-01-13 16:17:06] Working on story: US-604
[2026-01-13 16:17:06] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_5_US-604.log)
[2026-01-13 16:18:21] SUCCESS: Story US-604 passed!
[2026-01-13 16:18:22] Changes committed
[2026-01-13 16:18:22] Progress: 4/10 stories completed
[2026-01-13 16:18:24] === Iteration 6/30 ===
[2026-01-13 16:18:24] Working on story: US-605
[2026-01-13 16:18:24] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_6_US-605.log)
[2026-01-13 16:23:39] SUCCESS: Story US-605 passed!

View File

@@ -12,84 +12,111 @@
</div>
<!-- Company & Period Selection (below header, above navigation) -->
<!-- US-605: Collapsible sections with localStorage persistence -->
<div v-if="companiesStore" class="drawer-selectors">
<!-- Company Selector -->
<div class="selector-group">
<label class="selector-label">Firma</label>
<!-- Company Selector (Collapsible) -->
<div class="selector-group" :class="{ 'is-collapsed': companyCollapsed }">
<!-- Collapsible Header -->
<button
class="selector-trigger"
@click="toggleCompanyDropdown"
:aria-expanded="companyDropdownOpen"
class="collapsible-header"
@click="toggleCompanyCollapsed"
:aria-expanded="!companyCollapsed"
>
<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>
<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>
</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>
<!-- 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>
<div v-if="filteredCompanies.length === 0" class="selector-empty">
<i class="pi pi-info-circle"></i>
<span>Nu s-au găsit firme</span>
<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>
</div>
</div>
</div>
</div>
</div>
<!-- Period Selector -->
<div v-if="periodStore && companiesStore.selectedCompany" class="selector-group">
<label class="selector-label">Perioada</label>
<!-- Period Selector (Collapsible) -->
<div v-if="periodStore && companiesStore.selectedCompany" class="selector-group" :class="{ 'is-collapsed': periodCollapsed }">
<!-- Collapsible Header -->
<button
class="selector-trigger"
@click="togglePeriodDropdown"
:aria-expanded="periodDropdownOpen"
class="collapsible-header"
@click="togglePeriodCollapsed"
:aria-expanded="!periodCollapsed"
>
<div class="selector-value">
<span class="selector-main">{{ selectedPeriodDisplay }}</span>
</div>
<i class="pi pi-chevron-down" :class="{ 'rotate-180': periodDropdownOpen }"></i>
<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>
</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>
<!-- 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>
</div>
<div v-if="availablePeriods.length === 0" class="selector-empty">
<i class="pi pi-info-circle"></i>
<span>Nu sunt perioade disponibile</span>
<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>
</div>
</div>
@@ -231,6 +258,7 @@ import { useRoute, useRouter } from 'vue-router'
/**
* MobileDrawerMenu - Material Design 3 inspired navigation drawer for mobile (v3)
* US-605: Added collapsible Firma/Perioada sections
*
* Props:
* - modelValue (v-model): Controls visibility of the drawer
@@ -318,6 +346,56 @@ 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()
}
// Computed properties for company selector
const selectedCompanyName = computed(() => {
return props.companiesStore?.selectedCompany?.name || 'Selectare firmă'
@@ -780,6 +858,68 @@ onMounted(() => {
font-size: var(--text-sm);
}
/* ================================================
Collapsible Sections (US-605)
================================================ */
.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 (scrollable)
================================================ */
@@ -1113,6 +1253,23 @@ 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);
}
@@ -1293,6 +1450,23 @@ 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);
}