diff --git a/scripts/ralph/prd.json b/scripts/ralph/prd.json
index 253bae0..76949b3 100644
--- a/scripts/ralph/prd.json
+++ b/scripts/ralph/prd.json
@@ -216,8 +216,8 @@
"Butoane în MobileActionBar fix jos pe mobil",
"npm run build passes"
],
- "passes": false,
- "notes": ""
+ "passes": true,
+ "notes": "Completed in iteration 12"
},
{
"id": "US-211",
diff --git a/scripts/ralph/progress.txt b/scripts/ralph/progress.txt
index 2f13a8c..be1c874 100644
--- a/scripts/ralph/progress.txt
+++ b/scripts/ralph/progress.txt
@@ -70,3 +70,9 @@ User Stories: 14 (US-201 to US-214)
[2026-01-12 12:36:47] Working on story: US-210
[2026-01-12 12:36:47] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_11_US-210.log)
[2026-01-12 12:38:10] SUCCESS: Story US-210 passed!
+[2026-01-12 12:38:10] Changes committed
+[2026-01-12 12:38:10] Progress: 11/14 stories completed
+[2026-01-12 12:38:12] === Iteration 12/100 ===
+[2026-01-12 12:38:12] Working on story: US-209
+[2026-01-12 12:38:12] Running Claude... (log: /workspace/roa2web/scripts/ralph/logs/iteration_12_US-209.log)
+[2026-01-12 12:40:29] SUCCESS: Story US-209 passed!
diff --git a/src/modules/data-entry/views/receipts/ReceiptCreateUnifiedView.vue b/src/modules/data-entry/views/receipts/ReceiptCreateUnifiedView.vue
index 4fe2e1b..00cb9c6 100644
--- a/src/modules/data-entry/views/receipts/ReceiptCreateUnifiedView.vue
+++ b/src/modules/data-entry/views/receipts/ReceiptCreateUnifiedView.vue
@@ -203,87 +203,11 @@
-
-
-
-
-
+
+
@@ -305,6 +229,7 @@ import { useCompanyStore } from '@data-entry/stores/sharedStores'
import UnifiedReceiptForm from '@data-entry/components/receipts/UnifiedReceiptForm.vue'
import MobileTopBar from '@shared/components/mobile/MobileTopBar.vue'
+import MobileActionBar from '@shared/components/mobile/MobileActionBar.vue'
import {
getDefaultUnifiedFormState,
legacyToUnifiedForm,
@@ -377,6 +302,130 @@ const canDelete = computed(() => {
return ['draft', 'rejected'].includes(receipt.value.status)
})
+// US-209: Receipt state for context-aware buttons
+// States: draft, pending_review, approved, rejected
+const receiptStatus = computed(() => {
+ return receipt.value?.status || 'draft'
+})
+
+// US-209: Permission check - for now allow all users to approve/reject
+// TODO: In future, check user.permissions array from auth store
+const canApproveReject = computed(() => {
+ // For now, all authenticated users can approve/reject
+ // In production, this would check: authStore.user?.permissions?.includes('approve_receipts')
+ return true
+})
+
+// US-209: Mobile Action Bar actions based on receipt state
+const mobileActionBarActions = computed(() => {
+ // Create/Edit mode buttons
+ if (!isViewMode.value) {
+ return [
+ {
+ label: 'Salvează Ciornă',
+ icon: 'pi pi-save',
+ severity: 'secondary',
+ handler: saveReceipt,
+ disabled: saving.value
+ },
+ {
+ label: 'Trimite',
+ icon: 'pi pi-send',
+ severity: 'success',
+ handler: submitForReviewMobile,
+ disabled: !canSubmit.value || submitting.value
+ }
+ ]
+ }
+
+ // View mode - context-aware based on status
+ const status = receiptStatus.value
+ const actions = []
+
+ switch (status) {
+ case 'draft':
+ // Draft: Salvează | Submit pentru Aprobare | Șterge
+ actions.push({
+ label: 'Editează',
+ icon: 'pi pi-pencil',
+ severity: 'secondary',
+ handler: goToEdit,
+ disabled: false
+ })
+ actions.push({
+ label: 'Trimite',
+ icon: 'pi pi-send',
+ severity: 'success',
+ handler: submitReceipt,
+ disabled: submitting.value
+ })
+ break
+
+ case 'pending_review':
+ // Pending: Salvează | Aprobă | Respinge (dacă are permisiuni)
+ if (canApproveReject.value) {
+ actions.push({
+ label: 'Validează',
+ icon: 'pi pi-check',
+ severity: 'success',
+ handler: approveReceipt,
+ disabled: approving.value
+ })
+ actions.push({
+ label: 'Respinge',
+ icon: 'pi pi-times',
+ severity: 'danger',
+ handler: openRejectDialog,
+ disabled: rejecting.value
+ })
+ }
+ break
+
+ case 'approved':
+ // Approved: doar vizualizare (butoane disabled sau ascunse)
+ // Show cancel approval button for admin users
+ actions.push({
+ label: 'Anulează Validarea',
+ icon: 'pi pi-replay',
+ severity: 'warning',
+ handler: confirmCancelApproval,
+ disabled: cancelling.value
+ })
+ break
+
+ case 'rejected':
+ // Rejected: Salvează (re-edit) | Re-submit
+ actions.push({
+ label: 'Editează',
+ icon: 'pi pi-pencil',
+ severity: 'primary',
+ handler: goToEdit,
+ disabled: false
+ })
+ actions.push({
+ label: 'Re-trimite',
+ icon: 'pi pi-send',
+ severity: 'success',
+ handler: submitReceipt,
+ disabled: submitting.value
+ })
+ break
+ }
+
+ return actions
+})
+
+// US-209: Check if MobileActionBar should be visible
+const showMobileActionBar = computed(() => {
+ if (!isMobile.value) return false
+
+ // In create/edit mode, always show
+ if (!isViewMode.value) return true
+
+ // In view mode, show only if there are actions
+ return mobileActionBarActions.value.length > 0
+})
+
// US-042: More menu items - contextual based on status
const moreMenuItems = computed(() => {
const items = []
@@ -1355,11 +1404,12 @@ const cancelApproval = async () => {
}
/* ========================================
- * US-041/US-105: Mobile Layout
- * Uses shared MobileTopBar component
+ * US-041/US-105/US-209: Mobile Layout
+ * Uses shared MobileTopBar + MobileActionBar components
* PRD Mobile Layout Tokens:
* - topBarHeight: 56px
* - bottomNavHeight: 56px
+ * - MobileActionBar: positioned at bottom: 56px (above MobileBottomNav)
* - touchTargetMin: 48px
* ======================================== */
@@ -1367,7 +1417,9 @@ const cancelApproval = async () => {
.receipt-unified-view.mobile-compose-layout {
padding: 0;
padding-top: 56px; /* Space for fixed MobileTopBar */
- padding-bottom: 80px; /* Space for fixed bottom action bar */
+ /* MobileActionBar sits at bottom: 56px, so we need:
+ 56px (action bar height ~56px) + 56px (bottom nav) = 112px */
+ padding-bottom: 120px; /* Space for MobileActionBar + MobileBottomNav */
max-width: 100%;
min-height: 100vh;
background: var(--surface-ground);
@@ -1378,52 +1430,6 @@ const cancelApproval = async () => {
display: none;
}
-/* Mobile Bottom Action Bar - Gmail Compose Style */
-.mobile-form-bottom-bar {
- position: fixed;
- bottom: 0;
- left: 0;
- right: 0;
- height: auto;
- min-height: 56px;
- background: var(--surface-card);
- border-top: 1px solid var(--surface-border);
- display: flex;
- align-items: center;
- justify-content: stretch;
- gap: var(--space-sm);
- padding: var(--space-sm) var(--space-md);
- z-index: 1000;
- box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
- /* Safe area for iOS notch/home indicator */
- padding-bottom: max(var(--space-sm), env(safe-area-inset-bottom));
- transition: transform var(--transition-normal), opacity var(--transition-normal);
-}
-
-/* Keyboard-aware: move bar above keyboard */
-.mobile-form-bottom-bar.keyboard-visible {
- position: absolute;
- transform: translateY(-100%);
-}
-
-/* Buttons in bottom bar - equal width */
-.mobile-form-bottom-bar .bottom-bar-btn {
- flex: 1;
- min-height: 48px; /* Touch target minimum */
- font-size: var(--text-sm);
- font-weight: var(--font-medium);
- white-space: nowrap;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: var(--space-xs);
-}
-
-/* Primary button emphasis */
-.mobile-form-bottom-bar .bottom-bar-btn.p-button-success {
- flex: 1.2; /* Slightly wider for primary action */
-}
-
/* Mobile content area adjustments */
.receipt-unified-view.mobile-compose-layout .rejection-message {
margin: var(--space-sm);
@@ -1446,25 +1452,4 @@ const cancelApproval = async () => {
border-left: none;
border-right: none;
}
-
-/* ========================================
- * Dark Mode Support for Mobile Bottom Bar
- * (MobileTopBar handles its own dark mode)
- * ======================================== */
-[data-theme="dark"] .mobile-form-bottom-bar {
- background: var(--surface-card);
- border-top-color: var(--surface-border);
- box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.3);
-}
-
-/* ========================================
- * System Preference Dark Mode Support
- * ======================================== */
-@media (prefers-color-scheme: dark) {
- :root:not([data-theme="light"]) .mobile-form-bottom-bar {
- background: var(--surface-card);
- border-top-color: var(--surface-border);
- box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.3);
- }
-}