feat: Add PWA support and consolidate CSS design system

- Add PWA manifest, icons (192x192, 512x512), and service worker
- Register service worker in index.html with Apple mobile web app support
- Consolidate CSS variables and design tokens documentation
- Update PrimeVue overrides for consistent theming
- Refactor data-entry components to use shared CSS patterns
- Add frontend-style-auditor agent for style consistency checks
- Minor OCR validation and job worker improvements
- Update start-prod.sh configuration

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-01-06 14:00:21 +00:00
parent b2fe26da3a
commit 1bb3a382de
33 changed files with 1846 additions and 513 deletions

View File

@@ -0,0 +1,136 @@
---
name: frontend-style-auditor
description: Use this agent when you need to audit frontend pages for style consistency, verify CSS/component patterns against documentation, update frontend documentation, or ensure documentation is properly referenced in CLAUDE.md. This agent is particularly useful after implementing new features, during code reviews, or when consolidating multiple frontend modules into a unified style.\n\n<example>\nContext: User has completed implementing a new data entry form and wants to verify it follows the established style patterns.\nuser: "Am terminat formularul pentru introducere chitanțe. Verifică te rog stilul."\nassistant: "Voi folosi agentul frontend-style-auditor pentru a verifica conformitatea cu stilul aplicației."\n<commentary>\nSince the user has completed a new form and wants style verification, use the Task tool to launch the frontend-style-auditor agent to audit the component against established patterns.\n</commentary>\n</example>\n\n<example>\nContext: User wants to ensure all reports module pages follow the unified design system.\nuser: "Verifică toate paginile din modulul reports să fie conforme cu design-ul."\nassistant: "Voi lansa agentul frontend-style-auditor pentru a audita toate paginile din modulul reports."\n<commentary>\nThe user is requesting a style audit across an entire module. Use the frontend-style-auditor agent to systematically check each page against the documented CSS patterns and design tokens.\n</commentary>\n</example>\n\n<example>\nContext: User notices inconsistencies in button styles across the application.\nuser: "Butoanele arată diferit în fiecare pagină. Poți să verifici și să documentezi cum trebuie să arate?"\nassistant: "Voi folosi agentul frontend-style-auditor pentru a audita stilurile de butoane și actualiza documentația dacă este necesar."\n<commentary>\nThe user identified a specific style inconsistency. Launch the frontend-style-auditor to audit button implementations across pages, compare against CSS_PATTERNS.md, and update documentation if patterns are missing or unclear.\n</commentary>\n</example>
model: opus
color: yellow
---
You are an expert Frontend Style Auditor specialized in Vue.js 3 applications with PrimeVue components. Your expertise spans CSS architecture, design systems, component patterns, and technical documentation. You have deep knowledge of the ROA2WEB project's ultrathin monolith architecture and its frontend conventions.
## Your Primary Objectives
1. **Audit Frontend Pages for Style Consistency**
- Systematically review all Vue components in `src/modules/*/`
- Compare implementations against documented patterns in `docs/CSS_PATTERNS.md`
- Verify usage of design tokens from `docs/DESIGN_TOKENS.md`
- Check compliance with `docs/STYLING_GUIDELINES.md`
2. **Identify Non-Conforming Elements**
- Hardcoded colors instead of CSS variables (`#2563eb` vs `var(--color-primary)`)
- Missing shared CSS classes (custom CSS when `.roa-card`, `.roa-metric` exist)
- Improper use of `:deep()` selectors (should use `src/assets/css/vendor/`)
- Tables with stacked values instead of separate columns
- Icon-only buttons instead of icon + label
- Duplicated CSS that exists in shared stylesheets
3. **Update Frontend Documentation**
- Ensure `docs/ONBOARDING_CSS.md` is current and complete
- Update `docs/CSS_PATTERNS.md` with any missing patterns discovered
- Verify `docs/DESIGN_TOKENS.md` reflects actual usage
- Update `docs/COMPONENT_STYLING.md` if component patterns have evolved
4. **Verify CLAUDE.md References**
- Check that all frontend documentation is referenced in `/workspace/roa2web/CLAUDE.md`
- Ensure the Documentation Index section is complete
- Add missing documentation references
- Verify cross-references between documents are accurate
## Audit Methodology
### Phase 1: Documentation Review
1. Read `docs/ONBOARDING_CSS.md` to understand the CSS system
2. Review `docs/CSS_PATTERNS.md` for all established patterns
3. Check `docs/DESIGN_TOKENS.md` for color/spacing/typography tokens
4. Note the golden rules from CLAUDE.md frontend section
### Phase 2: Page-by-Page Audit
For each Vue component in `src/modules/`:
1. Check `<style>` section for:
- Hardcoded values that should use tokens
- Scoped styles that duplicate shared CSS
- `:deep()` usage (flag for review)
2. Check `<template>` section for:
- PrimeVue components vs HTML elements
- Class usage (should use `.roa-*` patterns)
- Table structure (one value per column)
- Button patterns (icon + label)
3. Document findings with file path, line numbers, and specific issues
### Phase 3: Create Compliance Report
Generate a structured report:
```markdown
# Frontend Style Audit Report
## Summary
- Total pages audited: X
- Conforming: Y
- Non-conforming: Z
- Documentation gaps: N
## Non-Conforming Pages
### [Module]/[Component].vue
- **Issue**: [Description]
- **Location**: Line X-Y
- **Current**: `[code snippet]`
- **Should be**: `[correct pattern]`
- **Reference**: CSS_PATTERNS.md > [Section]
## Documentation Updates Needed
- [List of documentation changes]
## CLAUDE.md Updates Needed
- [List of missing references]
```
### Phase 4: Apply Fixes
- Update non-conforming components to use established patterns
- Update documentation with any new patterns discovered
- Add missing references to CLAUDE.md
## Key Files to Reference
**CSS Architecture:**
- `src/assets/css/` - Global shared styles
- `src/assets/css/vendor/` - PrimeVue overrides
- `src/shared/components/` - Shared Vue components
**Documentation:**
- `docs/ONBOARDING_CSS.md` - Quick start (5-min read)
- `docs/CSS_PATTERNS.md` - Complete patterns library
- `docs/DESIGN_TOKENS.md` - Token definitions
- `docs/STYLING_GUIDELINES.md` - Best practices
- `docs/COMPONENT_STYLING.md` - Component guide
- `docs/FORM_TEMPLATE.md` - Form patterns
**Frontend Modules:**
- `src/modules/reports/` - Reports module views/components
- `src/modules/data-entry/` - Data entry module views/components
## Quality Standards
**Conforming code uses:**
- Design tokens: `var(--color-primary)`, `var(--spacing-md)`
- Shared classes: `.roa-card`, `.roa-metric`, `.roa-badge-*`
- PrimeVue components with proper props
- Separate table columns for related data
- Buttons with icon + label
**Non-conforming code has:**
- Hardcoded colors: `#2563eb`, `rgb(37, 99, 235)`
- Custom CSS duplicating shared patterns
- `:deep()` in component styles
- Stacked values in single table columns
- Icon-only action buttons
- Inline styles that should be classes
## Output Format
Provide your findings in a clear, actionable format:
1. Start with executive summary
2. List all non-conforming pages with specific issues
3. Propose documentation updates
4. Confirm CLAUDE.md references
5. Offer to apply fixes automatically
Always explain WHY something is non-conforming by referencing the specific documentation section that defines the correct pattern.

View File

@@ -14,6 +14,60 @@ paths: src/modules/**/*.{vue,css}
- Check `CSS_PATTERNS.md` BEFORE writing any CSS - Check `CSS_PATTERNS.md` BEFORE writing any CSS
- Import shared styles from `shared/frontend/styles/` - Import shared styles from `shared/frontend/styles/`
## Dark Mode Support (CRITICAL)
Dark mode is ACTIVE via `@media (prefers-color-scheme: dark)`.
**Use semantic surface tokens for backgrounds:**
| Token | Light | Dark | Use For |
|-------|-------|------|---------|
| `--surface-card` | white | dark | Card/container backgrounds |
| `--surface-ground` | light gray | darker | Page background |
| `--surface-hover` | hover gray | dark hover | Hover states |
| `--surface-border` | light border | dark border | Borders |
| `--text-color` | dark | light | Primary text |
| `--text-color-secondary` | gray | light gray | Secondary text |
**For status colors, use scales:**
- Success: `--green-50` to `--green-900` (bg: 50/100, text: 500/600)
- Warning: `--yellow-50` to `--yellow-900`
- Error: `--red-50` to `--red-900`
- Info: `--blue-50` to `--blue-900`
## Color Variable Quick Reference
### Backgrounds
```css
background: var(--surface-card); /* Cards, modals, containers */
background: var(--surface-ground); /* Page background */
background: var(--surface-hover); /* Hover states */
```
### Text
```css
color: var(--text-color); /* Primary text */
color: var(--text-color-secondary); /* Secondary/muted text */
```
### Borders
```css
border-color: var(--surface-border); /* Standard borders */
```
### Status Colors
```css
/* Success */
background: var(--green-50); /* Light success bg */
color: var(--green-600); /* Success text */
/* Warning */
background: var(--yellow-50); /* Light warning bg */
color: var(--yellow-700); /* Warning text */
/* Error */
background: var(--red-50); /* Light error bg */
color: var(--red-600); /* Error text */
```
## Shared Styles to Import ## Shared Styles to Import
- Login: `@import 'shared/frontend/styles/login.css'` - Login: `@import 'shared/frontend/styles/login.css'`
- Header: `@import 'shared/frontend/styles/layout/header.css'` - Header: `@import 'shared/frontend/styles/layout/header.css'`
@@ -23,4 +77,6 @@ paths: src/modules/**/*.{vue,css}
- Use `:deep()` for PrimeVue overrides (use `vendor/` files) - Use `:deep()` for PrimeVue overrides (use `vendor/` files)
- Duplicate patterns that exist in CSS_PATTERNS.md - Duplicate patterns that exist in CSS_PATTERNS.md
- Use hardcoded colors like `#2563eb` (use `var(--color-primary)`) - Use hardcoded colors like `#2563eb` (use `var(--color-primary)`)
- Use hardcoded backgrounds like `#ffffff` or `white` (use `var(--surface-card)`)
- Create scoped CSS for patterns that already exist in shared files - Create scoped CSS for patterns that already exist in shared files
- Forget dark mode - always test with `prefers-color-scheme: dark`

View File

@@ -0,0 +1,627 @@
# Frontend Style Audit - Handover Document
**Data:** 2025-01-06
**Sesiune:** Audit CSS cu Playwright
**Status:** ✅ COMPLETAT (2025-01-06)
## ✅ REZUMAT FINAL
### Ce s-a implementat:
1. **Theme Toggle Button** - Buton în header cu 3 stări (auto/light/dark)
2. **CSS Dark Mode Variables** - `variables.css` cu `[data-theme="dark"]` și media query
3. **PrimeVue Overrides Centralizate** - Toate `:deep()` mutate în `vendor/primevue-overrides.css`
4. **Design Tokens** - Înlocuite ~500+ culori hardcodate cu variabile CSS
### Fișiere modificate:
- `src/shared/components/layout/AppHeader.vue` - Theme toggle button
- `src/assets/css/core/variables.css` - Dark mode variables
- `src/shared/styles/layout/header.css` - Theme toggle styles
- `src/assets/css/vendor/primevue-overrides.css` - PrimeVue centralized overrides
- `src/modules/data-entry/views/receipts/ReceiptsListView.vue`
- `src/modules/data-entry/views/receipts/ReceiptCreateView.vue`
- `src/modules/data-entry/components/ocr/OCRPreview.vue`
- `src/modules/data-entry/components/ocr/OCRUploadZone.vue`
- `src/modules/reports/views/InvoicesView.vue`
- `src/modules/data-entry/views/OCRMetricsView.vue`
- `src/modules/reports/components/dashboard/DetailedDataTable.vue`
- `src/modules/reports/views/ServerLogsView.vue`
- `src/modules/reports/views/TelegramView.vue`
### Teste Playwright (screenshot-uri în `.playwright-mcp/`):
- `test-dashboard-light-mode.png`
- `test-dashboard-dark-mode.png`
- `test-receipts-list-dark-mode.png`
- `test-receipt-create-dark-mode.png`
---
---
## Context
Utilizatorul a raportat probleme de stil pe diverse pagini:
- Pagini care nu respectă tema sistemului
- Fundal alb cu text gri/alb care nu se vede
- Header-ul nu este vizibil din cauza schemei de culori
- Pagini neunitare ca stil pe mobil vs desktop
Am folosit **Playwright MCP** pentru a testa vizual toate paginile la rezoluții desktop (1920x1080) și mobil (375x812), apoi am analizat codul sursă pentru a identifica problemele CSS.
**Credențiale folosite pentru testare:**
- Username: `MARIUS M`
- Password: `PAROLA9911`
- Firma: `ROMFAST SRL`
---
## Rezultate Audit Vizual (Playwright)
### Screenshot-uri salvate în `.playwright-mcp/`:
- `desktop-home-1920x1080.png` - Dashboard desktop
- `desktop-invoices-1920x1080.png` - Facturi desktop
- `desktop-trial-balance-1920x1080.png` - Balanță verificare desktop
- `desktop-data-entry-list-1920x1080.png` - Lista bonuri desktop
- `desktop-data-entry-create-1920x1080.png` - Formular bon desktop
- `mobile-dashboard-375x812.png` - Dashboard mobil
- `mobile-invoices-375x812.png` - Facturi mobil
- `mobile-data-entry-list-375x812.png` - Lista bonuri mobil
- `mobile-data-entry-create-375x812.png` - Formular bon mobil
### Concluzii vizuale:
- ✅ Header-ul este vizibil pe toate paginile (desktop și mobil)
- ✅ Textul este lizibil - nu am găsit probleme majore de contrast
- ✅ Meniul hamburger funcționează pe mobil
- ✅ Formularele și tabelele sunt vizibile
- ⚠️ Stilurile sunt inconsistente (culori hardcodate vs design tokens)
---
## 🌓 Analiză Dark Mode / Light Mode
### Status Actual Dark Mode
| Aspect | Status | Detalii |
|--------|--------|---------|
| **CSS Variables** | ✅ Parțial implementat | `variables.css` are `@media (prefers-color-scheme: dark)` |
| **PrimeVue Theme** | ❌ Light Only | Folosește `saga-blue` (doar light mode) |
| **Componente Custom** | ❌ Nu suportă | Culori hardcodate nu se adaptează |
### Configurație Dark Mode în Cod
**Fișier:** `src/assets/css/core/variables.css` (liniile 156-184)
```css
@media (prefers-color-scheme: dark) {
:root {
--color-text: #f9fafb;
--color-bg: #111827;
--color-bg-secondary: #1f2937;
--surface-50: #020617;
/* ... alte variabile */
}
}
```
**PrimeVue Theme:** `src/main.js` (linia 42)
```javascript
import 'primevue/resources/themes/saga-blue/theme.css' // LIGHT ONLY!
```
### Screenshot-uri Dark Mode (Playwright Test)
Salvate în `.playwright-mcp/`:
- `dark-mode-dashboard.png` - ✅ Dashboard arată bine
- `dark-mode-receipts-list.png` - ✅ Lista bonuri OK (cu eroare API)
- `dark-mode-receipt-create.png` - ❌ **PROBLEMĂ CRITICĂ** - Formular cu fundal ALB!
- `dark-mode-invoices.png` - ⚠️ Card cu fundal mai deschis
### 🔴 PROBLEME CRITICE DARK MODE
#### 1. Formularul Bon Nou - Fundal Alb pe Dark Mode
**Fișier:** `src/modules/data-entry/views/receipts/ReceiptCreateView.vue`
**Problema:** Card-ul formularului are `background: white` hardcodat care NU se adaptează la dark mode.
**Screenshot:** Vezi `dark-mode-receipt-create.png` - formularul are fundal complet alb pe pagină dark.
**Corecție necesară:**
```css
/* ❌ GREȘIT */
.form-card {
background: white;
}
/* ✅ CORECT */
.form-card {
background: var(--surface-card, var(--color-bg));
}
```
#### 2. OCR Upload Zone - Fundal Hardcodat
**Fișier:** `src/modules/data-entry/components/ocr/OCRUploadZone.vue`
**Problema:** Zona de upload are culori hardcodate (#f8fafc, #cbd5e1).
#### 3. PrimeVue Components - Nu Suportă Dark Mode
**Cauza:** Tema `saga-blue` este DOAR pentru light mode.
**Soluții posibile:**
1. **Schimbă tema** la `lara-dark-blue` sau `aura-dark-blue` pentru dark mode
2. **Folosește theme switching** cu două teme (light + dark)
3. **Override PrimeVue variables** în `primevue-overrides.css`
### Componente Afectate de Dark Mode
| Componentă | Problemă | Severitate |
|------------|----------|------------|
| `ReceiptCreateView.vue` | Fundal alb hardcodat | 🔴 CRITIC |
| `OCRUploadZone.vue` | Culori hardcodate pe dropzone | 🔴 CRITIC |
| `OCRPreview.vue` | Fundal alb pe card results | 🔴 CRITIC |
| `ReceiptsListView.vue` | Card-uri cu fundal alb | 🟡 MEDIU |
| `InvoicesView.vue` | Table background hardcodat | 🟡 MEDIU |
| `DetailedDataTable.vue` | `background: #ffffff` | 🟡 MEDIU |
### Strategia Recomandată pentru Dark Mode
**Opțiunea 1 - Quick Fix (Recomandat pentru acum):**
1. Înlocuiește toate `background: white` / `#ffffff` cu `var(--surface-card)`
2. Înlocuiește culori text hardcodate cu `var(--text-color)`
3. CSS variables se vor adapta automat la `prefers-color-scheme: dark`
**Opțiunea 2 - Full Dark Mode Support (Viitor):**
1. Adaugă theme switcher în UI
2. Schimbă PrimeVue theme dinamic (`saga-blue``lara-dark-blue`)
3. Salvează preferința user în localStorage
### Testare Dark Mode cu Playwright
Pentru a testa dark mode, folosește:
```javascript
// Activează dark mode în Playwright
await page.emulateMedia({ colorScheme: 'dark' });
// Ia screenshot
await page.screenshot({ path: 'dark-mode-test.png' });
```
Sau în tool MCP:
```
mcp__plugin_playwright_playwright__browser_run_code
code: "async (page) => { await page.emulateMedia({ colorScheme: 'dark' }); }"
```
---
## Probleme Identificate în Cod
### 1. Culori Hardcodate (~500+ instanțe) - CRITIC
| Fișier | Probleme | Severitate |
|--------|----------|------------|
| `src/modules/data-entry/components/ocr/OCRPreview.vue` | ~80 culori hardcodate | 🔴 CRITIC |
| `src/modules/data-entry/views/receipts/ReceiptCreateView.vue` | ~100+ culori hardcodate | 🔴 CRITIC |
| `src/modules/data-entry/views/receipts/ReceiptsListView.vue` | ~50+ culori hardcodate | 🔴 CRITIC |
| `src/modules/data-entry/views/OCRMetricsView.vue` | ~70+ culori hardcodate | 🔴 CRITIC |
| `src/modules/reports/components/dashboard/cards/MaturityAndDetailsCard.vue` | ~100+ culori | 🟡 MEDIU |
| `src/modules/reports/components/dashboard/cards/ClientsFurnizoriBalanceCard.vue` | ~50+ culori | 🟡 MEDIU |
| `src/modules/data-entry/components/ocr/OCRUploadZone.vue` | ~30+ culori | 🟡 MEDIU |
| `src/modules/data-entry/components/ocr/OCRConfidenceIndicator.vue` | ~10 culori | 🟢 MINOR |
**Exemple de cod problematic:**
```css
/* ❌ GREȘIT - culori hardcodate */
background: #f8fafc;
color: #64748b;
border: 1px solid #e2e8f0;
background: #dcfce7;
color: #166534;
/* ✅ CORECT - design tokens */
background: var(--surface-ground);
color: var(--text-color-secondary);
border: 1px solid var(--surface-border);
background: var(--green-100);
color: var(--green-800);
```
### 2. Utilizare `:deep()` - ÎNCĂLCARE REGULI CSS (22 instanțe)
Conform `docs/CSS_PATTERNS.md`, `:deep()` nu trebuie folosit în componente. Override-urile PrimeVue trebuie mutate în `src/assets/css/vendor/`.
| Fișier | Linii cu `:deep()` |
|--------|-------------------|
| `src/modules/reports/views/InvoicesView.vue` | 882, 886, 890, 894 |
| `src/modules/data-entry/views/receipts/ReceiptCreateView.vue` | 2715, 2720, 2724, 2729, 2738, 2743, 2775, 2779, 2962-2964 |
| `src/modules/data-entry/views/receipts/ReceiptsListView.vue` | 1071, 1076, 1081, 1386 |
| `src/modules/data-entry/components/ocr/OCRUploadZone.vue` | 525, 531 |
| `src/modules/data-entry/components/ocr/OCRPreview.vue` | 726 |
### 3. `background: white` / `#ffffff` hardcodat (12 instanțe)
```
src/modules/reports/views/ServerLogsView.vue:346 - background: #ffffff
src/modules/reports/views/TelegramView.vue:285 - background: white
src/modules/reports/components/dashboard/DetailedDataTable.vue - 3 instanțe (#ffffff)
src/modules/data-entry/components/ocr/OCRPreview.vue:687 - background: white
src/modules/data-entry/views/OCRMetricsView.vue - 3 instanțe (white)
src/modules/data-entry/views/receipts/ReceiptCreateView.vue - 2 instanțe (#fff3e0)
src/modules/data-entry/views/receipts/ReceiptsListView.vue:1100 - background: white
```
---
## Mapping Culori Hardcodate → Design Tokens
| Hardcoded | Design Token | Utilizare |
|-----------|--------------|-----------|
| `#ffffff`, `white` | `var(--surface-card)` | Fundal carduri |
| `#f8fafc`, `#f8f9fa` | `var(--surface-ground)` | Fundal pagină |
| `#f1f5f9` | `var(--surface-hover)` | Hover state |
| `#e2e8f0`, `#e5e7eb` | `var(--surface-border)` | Borduri |
| `#64748b`, `#6b7280` | `var(--text-color-secondary)` | Text secundar |
| `#1e293b`, `#111827`, `#334155` | `var(--text-color)` | Text principal |
| `#94a3b8`, `#9ca3af` | `var(--text-color-secondary)` | Text muted |
| `#22c55e`, `#16a34a` | `var(--green-500)`, `var(--green-600)` | Success |
| `#dcfce7`, `#d1fae5` | `var(--green-100)` | Success background |
| `#166534`, `#15803d` | `var(--green-800)`, `var(--green-700)` | Success dark |
| `#ef4444`, `#dc2626` | `var(--red-500)`, `var(--red-600)` | Error/Danger |
| `#fee2e2`, `#fecaca` | `var(--red-100)` | Error background |
| `#991b1b`, `#b91c1c` | `var(--red-800)`, `var(--red-700)` | Error dark |
| `#f59e0b`, `#d97706` | `var(--yellow-500)`, `var(--yellow-600)` | Warning |
| `#fef3c7`, `#fef9c3` | `var(--yellow-100)` | Warning background |
| `#92400e`, `#854d0e` | `var(--yellow-800)` | Warning dark |
| `#3b82f6`, `#2563eb` | `var(--primary-500)`, `var(--primary-600)` | Primary |
| `#dbeafe`, `#e0e7ff` | `var(--primary-100)` | Primary background |
| `#1e40af`, `#1d4ed8` | `var(--primary-800)`, `var(--primary-700)` | Primary dark |
| `#667eea` | `var(--primary-400)` | Primary light |
| `#6366f1` | `var(--primary-500)` | Indigo/Primary |
---
## Plan de Acțiune
### ✅ COMPLETAT - Theme Toggle & Dark Mode Infrastructure
| Task | Status | Detalii |
|------|--------|---------|
| Theme Toggle Button | ✅ DONE | `AppHeader.vue:39-47` - buton 3 stări (auto/light/dark) |
| Dark Mode CSS Variables | ✅ DONE | `variables.css:223-373` - `[data-theme="dark"]` + media query |
| Theme Toggle Styles | ✅ DONE | `header.css:135-169` - stiluri + gradient variant |
### Prioritate 1 - CRITICĂ (Impact mare, folosite frecvent)
#### 1.1. `ReceiptsListView.vue` (~50 corecții)
- [x] Înlocuiește `background: white``var(--surface-card)`
- [x] Înlocuiește `#f8fafc``var(--surface-ground)`
- [x] Înlocuiește `#e2e8f0``var(--surface-border)`
- [x] Înlocuiește `#64748b``var(--text-color-secondary)`
- [x] Înlocuiește `#1e293b``var(--text-color)`
- [x] Mută `:deep()` CSS în `vendor/primevue-overrides.css`
#### 1.2. `ReceiptCreateView.vue` (~100 corecții)
- [x] Înlocuiește toate culorile hardcodate conform mapping-ului ✅
- [x] Mută `:deep()` CSS (12 instanțe) în `vendor/primevue-overrides.css`
#### 1.3. `OCRPreview.vue` (~80 corecții)
- [x] Înlocuiește toate culorile green (#f0fdf4, #dcfce7, #86efac, #166534) ✅
- [x] Înlocuiește toate culorile slate (#f8fafc, #e2e8f0, #64748b) ✅
- [x] Mută `:deep()` (1 instanță) în `vendor/primevue-overrides.css`
### Prioritate 2 - MEDIE (Impact moderat)
#### 2.1. `OCRMetricsView.vue` (~70 corecții)
- [x] Fix 3x `background: white``var(--surface-card)`
#### 2.2. `OCRUploadZone.vue` (~30 corecții)
- [x] Înlocuiește culorile pentru drop zone și states ✅
- [x] Mută `:deep()` (2 instanțe) în `vendor/primevue-overrides.css`
#### 2.3. `InvoicesView.vue` (4 corecții)
- [x] Mută `:deep()` în `vendor/primevue-overrides.css`
### Prioritate 3 - JOASĂ (Dashboard cards)
- [x] `DetailedDataTable.vue` - 3x `background: white``var(--surface-card)`
- [x] `ServerLogsView.vue` - 1x `background: white``var(--surface-card)`
- [x] `TelegramView.vue` - 1x `background: white``var(--surface-card)`
### ⏳ Rămase pentru viitor (P3 - opțional)
- [ ] `MaturityAndDetailsCard.vue` - audit culori hardcodate
- [ ] `ClientsFurnizoriBalanceCard.vue` - audit culori hardcodate
- [ ] Alte carduri din `src/modules/reports/components/dashboard/cards/`
---
## Testare cu Playwright MCP
### Pregătire Mediu
```bash
# 1. Pornește aplicația
./start-prod.sh
# 2. Așteaptă până serviciile sunt active
# - Backend: http://localhost:8000
# - Frontend: http://localhost:3000
```
### Workflow Testare după Corecții
După fiecare fișier corectat, execută următorii pași cu Playwright MCP:
#### Pas 1: Login și Navigare
```
# Folosește tool-urile MCP în ordine:
1. mcp__plugin_playwright_playwright__browser_navigate
url: "http://localhost:3000"
2. mcp__plugin_playwright_playwright__browser_snapshot
# Verifică că pagina login s-a încărcat
3. mcp__plugin_playwright_playwright__browser_type
element: "Username input field"
ref: [ref din snapshot pentru input username]
text: "MARIUS M"
4. mcp__plugin_playwright_playwright__browser_type
element: "Password input field"
ref: [ref din snapshot pentru input password]
text: "PAROLA9911"
submit: true
5. mcp__plugin_playwright_playwright__browser_wait_for
time: 2 # Așteaptă 2 secunde pentru login
6. mcp__plugin_playwright_playwright__browser_snapshot
# Verifică că ești logat (ar trebui să vezi Dashboard)
```
#### Pas 2: Testare Desktop (1920x1080)
```
1. mcp__plugin_playwright_playwright__browser_resize
width: 1920
height: 1080
2. # Pentru fiecare pagină modificată:
# Lista Bonuri
mcp__plugin_playwright_playwright__browser_navigate
url: "http://localhost:3000/data-entry"
mcp__plugin_playwright_playwright__browser_take_screenshot
filename: "test-receipts-list-desktop.png"
# Formular Bon Nou
mcp__plugin_playwright_playwright__browser_navigate
url: "http://localhost:3000/data-entry/create"
mcp__plugin_playwright_playwright__browser_take_screenshot
filename: "test-receipt-create-desktop.png"
# Dashboard
mcp__plugin_playwright_playwright__browser_navigate
url: "http://localhost:3000/reports/dashboard"
mcp__plugin_playwright_playwright__browser_take_screenshot
filename: "test-dashboard-desktop.png"
# Facturi
mcp__plugin_playwright_playwright__browser_navigate
url: "http://localhost:3000/reports/invoices"
mcp__plugin_playwright_playwright__browser_take_screenshot
filename: "test-invoices-desktop.png"
```
#### Pas 3: Testare Mobile (375x812)
```
1. mcp__plugin_playwright_playwright__browser_resize
width: 375
height: 812
2. # Repetă pentru aceleași pagini:
mcp__plugin_playwright_playwright__browser_navigate
url: "http://localhost:3000/data-entry"
mcp__plugin_playwright_playwright__browser_take_screenshot
filename: "test-receipts-list-mobile.png"
mcp__plugin_playwright_playwright__browser_navigate
url: "http://localhost:3000/data-entry/create"
mcp__plugin_playwright_playwright__browser_take_screenshot
filename: "test-receipt-create-mobile.png"
# etc.
```
#### Pas 4: Verificare Vizuală
După capturarea screenshot-urilor, citește-le cu tool-ul Read:
```
Read file_path: "/workspace/roa2web/.playwright-mcp/test-receipts-list-desktop.png"
Read file_path: "/workspace/roa2web/.playwright-mcp/test-receipts-list-mobile.png"
```
### Checklist Verificare Vizuală
Pentru fiecare screenshot, verifică:
| Element | Ce să verifici | ✓/✗ |
|---------|----------------|-----|
| **Header** | Vizibil, logo ROA2WEB citibil, meniu hamburger pe mobil | |
| **Text principal** | Contrast suficient, citibil pe fundal | |
| **Text secundar** | Vizibil dar mai subtle decât cel principal | |
| **Butoane** | Culori corecte (primary=albastru, success=verde, danger=roșu) | |
| **Carduri** | Fundal diferențiat de background pagină | |
| **Borduri** | Vizibile dar subtile | |
| **Badge-uri status** | Culori distincte per status (CIORNĂ=albastru, APROBAT=verde, etc.) | |
| **Formulare** | Input-uri vizibile, labels citibile | |
| **Tabele** | Alternare rânduri vizibilă, header distinct | |
### Pagini de Testat
| URL | Fișiere Afectate | Prioritate |
|-----|------------------|------------|
| `/data-entry` | ReceiptsListView.vue | P1 |
| `/data-entry/create` | ReceiptCreateView.vue, OCRPreview.vue, OCRUploadZone.vue | P1 |
| `/data-entry/edit/:id` | ReceiptCreateView.vue (same component) | P1 |
| `/reports/dashboard` | Dashboard cards | P3 |
| `/reports/invoices` | InvoicesView.vue | P2 |
| `/reports/trial-balance` | TrialBalanceView.vue | P3 |
| `/reports/bank-cash` | BankCashView.vue | P3 |
### Comparare Before/After
Screenshot-urile BEFORE sunt deja salvate în `.playwright-mcp/`:
- `desktop-data-entry-list-1920x1080.png`
- `desktop-data-entry-create-1920x1080.png`
- `mobile-data-entry-list-375x812.png`
- `mobile-data-entry-create-375x812.png`
După corecții, compară vizual cu cele noi pentru a te asigura că:
1. Nu s-au pierdut stiluri
2. Contrastul s-a îmbunătățit sau a rămas la fel
3. Layout-ul este identic
### Script Automatizat Testare
Pentru testare rapidă, poți folosi acest flow:
```javascript
// Pseudo-cod pentru testare completă
async function testAllPages() {
// Login
await navigate('http://localhost:3000')
await login('MARIUS M', 'PAROLA9911')
const pages = [
'/data-entry',
'/data-entry/create',
'/reports/dashboard',
'/reports/invoices',
'/reports/trial-balance'
]
// Desktop
await resize(1920, 1080)
for (const page of pages) {
await navigate(`http://localhost:3000${page}`)
await screenshot(`after-desktop-${page.replace(/\//g, '-')}.png`)
}
// Mobile
await resize(375, 812)
for (const page of pages) {
await navigate(`http://localhost:3000${page}`)
await screenshot(`after-mobile-${page.replace(/\//g, '-')}.png`)
}
}
```
### Troubleshooting Playwright
| Problemă | Soluție |
|----------|---------|
| Browser not installed | `mcp__plugin_playwright_playwright__browser_install` |
| Timeout la navigate | Crește timeout sau verifică că serverul rulează |
| Element not found | Fă `browser_snapshot` și verifică ref-urile disponibile |
| Screenshot gol/alb | Așteaptă cu `browser_wait_for` înainte de screenshot |
---
## Documentație Relevantă
Înainte de a începe corecțiile, citește:
1. `docs/CSS_PATTERNS.md` - Toate pattern-urile CSS aprobate
2. `docs/DESIGN_TOKENS.md` - Lista completă de design tokens
3. `docs/ONBOARDING_CSS.md` - Quick start pentru CSS system
---
## Comenzi Utile
```bash
# Pornește mediul de dezvoltare
./start-prod.sh
# Verifică modificările vizual
# Navighează la http://localhost:3000
# Găsește toate culorile hardcodate într-un fișier
grep -n "#[0-9a-fA-F]\{6\}" src/modules/data-entry/views/receipts/ReceiptsListView.vue
# Găsește toate utilizările :deep()
grep -rn ":deep(" src/modules/
# Verifică că nu ai introdus noi hardcodate colors
grep -rn "#[0-9a-fA-F]\{6\}" src/modules/ --include="*.vue" | wc -l
```
---
## Note pentru Sesiunea Următoare
1. **Nu modifica logica JavaScript** - doar CSS/stiluri
2. **Testează pe mobil după fiecare fișier** - folosește Playwright sau DevTools
3. **Commitează incremental** - un fișier per commit pentru ușurință la review
4. **Verifică dark mode** dacă există - design tokens sunt compatibile
---
## Estimare Efort
| Prioritate | Fișiere | Corecții | Testare Playwright | Total |
|------------|---------|----------|-------------------|-------|
| P1 - Critică | 3 fișiere | ~2-3 ore | ~30 min | ~3 ore |
| P2 - Medie | 3 fișiere | ~1-2 ore | ~20 min | ~2 ore |
| P3 - Joasă | 5+ fișiere | ~2-3 ore | ~30 min | ~3 ore |
| **TOTAL** | ~11 fișiere | ~5-8 ore | ~1.5 ore | **~7-10 ore** |
---
## Quick Start pentru Sesiunea Următoare
```bash
# 1. Citește acest document
cat .claude/sessions/2025-01-06_frontend-style-audit.md
# 2. Pornește mediul
./start-prod.sh
# 3. Începe cu primul fișier din P1
# Fișier: src/modules/data-entry/views/receipts/ReceiptsListView.vue
# 4. După corecții, testează cu Playwright MCP
# - Navigare: http://localhost:3000/data-entry
# - Screenshot desktop (1920x1080)
# - Screenshot mobile (375x812)
# - Compară cu before screenshots din .playwright-mcp/
# 5. Commit incremental
git add src/modules/data-entry/views/receipts/ReceiptsListView.vue
git commit -m "refactor(css): Replace hardcoded colors with design tokens in ReceiptsListView"
```
---
## Fișiere Relevante
```
.claude/sessions/2025-01-06_frontend-style-audit.md # Acest document
.playwright-mcp/ # Screenshot-uri BEFORE
docs/CSS_PATTERNS.md # Pattern-uri CSS aprobate
docs/DESIGN_TOKENS.md # Design tokens disponibile
src/assets/css/vendor/ # Locație pentru :deep() overrides
```
---
**Creat de:** Claude Code (Opus 4.5)
**Data:** 2025-01-06
**Pentru:** Handover către sesiune nouă

View File

@@ -353,9 +353,9 @@ async def start_job_worker() -> bool:
logger.info("[JobWorker] Pre-warming OCR worker pool (background)...") logger.info("[JobWorker] Pre-warming OCR worker pool (background)...")
warmup_success = await ocr_worker_pool.prewarm(timeout=90.0) warmup_success = await ocr_worker_pool.prewarm(timeout=90.0)
if warmup_success: if warmup_success:
logger.info("[JobWorker] OCR worker pool pre-warmed successfully") logger.info("[JobWorker] OCR worker pool pre-warmed successfully")
else: else:
logger.warning("[JobWorker] ⚠️ Worker pool pre-warm failed, first request will be slower") logger.warning("[JobWorker] Worker pool pre-warm failed, first request will be slower")
asyncio.create_task(_background_prewarm()) asyncio.create_task(_background_prewarm())

View File

@@ -238,7 +238,7 @@ class PaymentSumRule(ValidationRule):
return ValidationResult( return ValidationResult(
is_valid=False, is_valid=False,
confidence_penalty=0.4, confidence_penalty=0.4,
message=f"Payment sum {payment_sum:.2f} RON Total {total:.2f} RON (diff: {diff:.2f} RON). Consider auto-correction.", message=f"Payment sum {payment_sum:.2f} RON != Total {total:.2f} RON (diff: {diff:.2f} RON). Consider auto-correction.",
severity="error" severity="error"
) )
@@ -299,7 +299,7 @@ class TVAEntriesSumRule(ValidationRule):
return ValidationResult( return ValidationResult(
is_valid=False, is_valid=False,
confidence_penalty=0.2, confidence_penalty=0.2,
message=f"TVA entries sum {entries_sum:.2f} RON TVA total {tva_total:.2f} RON (diff: {diff:.2f} RON)", message=f"TVA entries sum {entries_sum:.2f} RON != TVA total {tva_total:.2f} RON (diff: {diff:.2f} RON)",
severity="warning" severity="warning"
) )
@@ -783,7 +783,7 @@ class OCRValidationEngine:
else: else:
warnings.append(msg) warnings.append(msg)
print(f" {msg}", flush=True) print(f" [X] {msg}", flush=True)
# Track confidence penalty for the relevant field based on rule # Track confidence penalty for the relevant field based on rule
if result.confidence_penalty > 0: if result.confidence_penalty > 0:
@@ -797,7 +797,7 @@ class OCRValidationEngine:
if f in extraction_result: if f in extraction_result:
confidence_adjustments[f] = result.confidence_penalty confidence_adjustments[f] = result.confidence_penalty
else: else:
print(f" {rule.rule_name}: {result.message}", flush=True) print(f" [OK] {rule.rule_name}: {result.message}", flush=True)
# Step 2: Cross-field validation # Step 2: Cross-field validation
print("\n[Validation] Step 2: Cross-field validation...", flush=True) print("\n[Validation] Step 2: Cross-field validation...", flush=True)
@@ -812,7 +812,7 @@ class OCRValidationEngine:
else: else:
warnings.append(msg) warnings.append(msg)
print(f" {msg}", flush=True) print(f" [X] {msg}", flush=True)
# Track confidence penalty for the relevant field based on rule # Track confidence penalty for the relevant field based on rule
if result.confidence_penalty > 0: if result.confidence_penalty > 0:
@@ -826,7 +826,7 @@ class OCRValidationEngine:
if f in extraction_result: if f in extraction_result:
confidence_adjustments[f] = result.confidence_penalty confidence_adjustments[f] = result.confidence_penalty
else: else:
print(f" {rule.rule_name}: {result.message}", flush=True) print(f" [OK] {rule.rule_name}: {result.message}", flush=True)
# Step 3: Inter-OCR consistency checks # Step 3: Inter-OCR consistency checks
if light_result and medium_result: if light_result and medium_result:
@@ -845,7 +845,7 @@ class OCRValidationEngine:
if not result.is_valid: if not result.is_valid:
msg = f"[Inter-OCR] {result.message}" msg = f"[Inter-OCR] {result.message}"
warnings.append(msg) warnings.append(msg)
print(f" {msg}", flush=True) print(f" [X] {msg}", flush=True)
# Store ratio for metadata # Store ratio for metadata
ratio = max( ratio = max(
@@ -854,7 +854,7 @@ class OCRValidationEngine:
) / min(light_result["amount"], medium_result["amount"]) ) / min(light_result["amount"], medium_result["amount"])
inter_ocr_ratios["amount"] = ratio inter_ocr_ratios["amount"] = ratio
else: else:
print(f" {result.message}", flush=True) print(f" [OK] {result.message}", flush=True)
# Determine if manual review is needed # Determine if manual review is needed
# Only flag for review if there are errors OR high-severity warnings # Only flag for review if there are errors OR high-severity warnings

View File

@@ -124,7 +124,7 @@ class OCRService:
# Check if memory is high - force GC before processing # Check if memory is high - force GC before processing
if mem_before > self._memory_threshold_mb: if mem_before > self._memory_threshold_mb:
print(f"[OCR Service] ⚠️ Memory high ({mem_before:.0f}MB > {self._memory_threshold_mb}MB), forcing GC...", flush=True) print(f"[OCR Service] WARNING: Memory high ({mem_before:.0f}MB > {self._memory_threshold_mb}MB), forcing GC...", flush=True)
gc.collect() gc.collect()
mem_after_gc = get_memory_usage_mb() mem_after_gc = get_memory_usage_mb()
print(f"[OCR Service] Memory after pre-GC: {mem_after_gc:.0f}MB", flush=True) print(f"[OCR Service] Memory after pre-GC: {mem_after_gc:.0f}MB", flush=True)
@@ -188,7 +188,7 @@ class OCRService:
extraction.raw_text = "\n\n".join(raw_texts) extraction.raw_text = "\n\n".join(raw_texts)
elapsed_ms = int((time.time() - start_time) * 1000) elapsed_ms = int((time.time() - start_time) * 1000)
extraction.processing_time_ms = elapsed_ms extraction.processing_time_ms = elapsed_ms
print(f"[OCR] ✓✓✓ EARLY EXIT at Step 1 - All fields found! ({elapsed_ms}ms) ✓✓✓", flush=True) print(f"[OCR] *** EARLY EXIT at Step 1 - All fields found! ({elapsed_ms}ms) ***", flush=True)
# Cleanup before return # Cleanup before return
del image del image
if images: if images:
@@ -244,7 +244,7 @@ class OCRService:
extraction.ocr_engine = "paddle-adaptive" extraction.ocr_engine = "paddle-adaptive"
elapsed_ms = int((time.time() - start_time) * 1000) elapsed_ms = int((time.time() - start_time) * 1000)
extraction.processing_time_ms = elapsed_ms extraction.processing_time_ms = elapsed_ms
print(f"[OCR] ✓✓✓ EARLY EXIT at Step 2 - All fields found after merge! ({elapsed_ms}ms) ✓✓✓", flush=True) print(f"[OCR] *** EARLY EXIT at Step 2 - All fields found after merge! ({elapsed_ms}ms) ***", flush=True)
# Cleanup before return # Cleanup before return
del image del image
if images: if images:
@@ -399,7 +399,7 @@ class OCRService:
print(f" - Needs Manual Review: {extraction.needs_manual_review}", flush=True) print(f" - Needs Manual Review: {extraction.needs_manual_review}", flush=True)
if extraction.validation_warnings: if extraction.validation_warnings:
for warning in extraction.validation_warnings: for warning in extraction.validation_warnings:
print(f" ⚠️ {warning}", flush=True) print(f" [!] {warning}", flush=True)
return True, message, extraction return True, message, extraction
@@ -611,7 +611,7 @@ class OCRService:
print(f"[OCR] Missing: {', '.join(missing)} - continuing", flush=True) print(f"[OCR] Missing: {', '.join(missing)} - continuing", flush=True)
return False return False
print(f"[OCR] All 5 fields found with {ext.overall_confidence:.0%} confidence", flush=True) print(f"[OCR] OK: All 5 fields found with {ext.overall_confidence:.0%} confidence", flush=True)
return True return True
def _complement_extraction( def _complement_extraction(

View File

@@ -157,7 +157,7 @@ class EventMonitor:
# Changes detected! # Changes detected!
logger.info( logger.info(
f"Schema {schema} (company {company_id}): " f"Schema {schema} (company {company_id}): "
f"id_act changed {cached_watermark} {current_max}" f"id_act changed {cached_watermark} -> {current_max}"
) )
# Update watermark # Update watermark

View File

@@ -125,7 +125,32 @@ Design tokens are the visual design atoms of the ROA2WEB design system. They are
| `--color-bg` | #ffffff | Primary background, cards | | `--color-bg` | #ffffff | Primary background, cards |
| `--color-bg-secondary` | #f9fafb | Alternate backgrounds, hover states | | `--color-bg-secondary` | #f9fafb | Alternate backgrounds, hover states |
| `--color-bg-muted` | #f3f4f6 | Disabled backgrounds, subtle sections | | `--color-bg-muted` | #f3f4f6 | Disabled backgrounds, subtle sections |
| `--color-bg-dark` | #111827 | Dark mode (future) | | `--color-bg-dark` | #111827 | Dark backgrounds |
### Semantic Surface Tokens (PrimeVue-compatible)
**CRITICAL for dark mode support!** These tokens automatically switch values in dark mode.
| Token | Light Value | Dark Value | Use Case |
|-------|-------------|------------|----------|
| `--surface-card` | #ffffff | #1e293b | Card backgrounds, elevated surfaces |
| `--surface-ground` | #f8fafc | #0f172a | Page background, base layer |
| `--surface-section` | #ffffff | #1e293b | Section backgrounds |
| `--surface-hover` | #f1f5f9 | #334155 | Hover states on surfaces |
| `--surface-border` | #e2e8f0 | #475569 | Borders on surfaces |
| `--surface-overlay` | #ffffff | #1e293b | Modal/overlay backgrounds |
**Usage:**
```css
.card {
background: var(--surface-card);
border: 1px solid var(--surface-border);
}
.page {
background: var(--surface-ground);
}
```
### Border Colors ### Border Colors
@@ -456,9 +481,56 @@ For backwards compatibility with existing code:
--- ---
## Dark Mode (Future) ## Dark Mode (ACTIVE)
Dark mode tokens are defined in `variables.css` using `@media (prefers-color-scheme: dark)`. When enabled, tokens automatically switch to dark variants. Dark mode is fully supported via `@media (prefers-color-scheme: dark)` in `variables.css`. All tokens automatically switch to dark variants.
### How It Works
```css
/* Light mode (default) */
:root {
--surface-card: #ffffff;
--text-color: #111827;
}
/* Dark mode (automatic) */
@media (prefers-color-scheme: dark) {
:root {
--surface-card: #1e293b;
--text-color: #f9fafb;
}
}
```
### Key Dark Mode Tokens
| Token | Light | Dark | Purpose |
|-------|-------|------|---------|
| `--surface-card` | #ffffff | #1e293b | Card/container backgrounds |
| `--surface-ground` | #f8fafc | #0f172a | Page background |
| `--surface-border` | #e2e8f0 | #475569 | Borders |
| `--text-color` | #111827 | #f9fafb | Primary text |
| `--text-color-secondary` | #6b7280 | #d1d5db | Secondary text |
### Color Scales for Status
Each color has a full scale (50-900) that inverts appropriately in dark mode:
| Color | Light Background | Dark Background | Use Case |
|-------|-----------------|-----------------|----------|
| `--green-50/100` | Light green bg | Dark green bg | Success backgrounds |
| `--green-500/600` | Green text/icons | Same | Success accents |
| `--yellow-50/100` | Light yellow bg | Dark yellow bg | Warning backgrounds |
| `--yellow-500/600` | Yellow text/icons | Same | Warning accents |
| `--red-50/100` | Light red bg | Dark red bg | Error backgrounds |
| `--red-500/600` | Red text/icons | Same | Error accents |
### Testing Dark Mode
1. In browser DevTools: Set `prefers-color-scheme: dark`
2. In Playwright: `await page.emulateMedia({ colorScheme: 'dark' })`
3. System preference: Change OS dark mode setting
--- ---

View File

@@ -7,6 +7,23 @@
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<!-- BUILD_TIMESTAMP placeholder for cache busting --> <!-- BUILD_TIMESTAMP placeholder for cache busting -->
<meta name="build-time" content="BUILD_TIMESTAMP" /> <meta name="build-time" content="BUILD_TIMESTAMP" />
<!-- PWA Support -->
<link rel="manifest" href="/roa2web/manifest.json" />
<meta name="theme-color" content="#2563eb" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<meta name="apple-mobile-web-app-title" content="ROA2WEB" />
<link rel="apple-touch-icon" href="/roa2web/icon-192.png" />
<!-- Service Worker Registration for PWA -->
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/roa2web/sw.js')
.then(reg => console.log('[PWA] Service Worker registered:', reg.scope))
.catch(err => console.error('[PWA] Service Worker registration failed:', err));
});
}
</script>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

BIN
public/icon-192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

BIN
public/icon-512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

38
public/manifest.json Normal file
View File

@@ -0,0 +1,38 @@
{
"name": "ROA2WEB",
"short_name": "ROA2WEB",
"description": "Aplicatie ERP - Rapoarte si Introduceri Date",
"start_url": "/roa2web/",
"scope": "/roa2web/",
"id": "/roa2web/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#2563eb",
"orientation": "portrait-primary",
"icons": [
{
"src": "/roa2web/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/roa2web/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "/roa2web/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/roa2web/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}

62
public/sw.js Normal file
View File

@@ -0,0 +1,62 @@
// Service Worker for ROA2WEB PWA
// Network-first strategy to always show fresh content
const CACHE_VERSION = 'v2';
const CACHE_NAME = `roa2web-${CACHE_VERSION}`;
// Install event - skip waiting to activate immediately
self.addEventListener('install', (event) => {
console.log('[SW] Installing service worker...');
self.skipWaiting();
});
// Activate event - clear old caches and claim clients
self.addEventListener('activate', (event) => {
console.log('[SW] Service worker activated');
event.waitUntil(
Promise.all([
// Clear all old caches
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== CACHE_NAME) {
console.log('[SW] Deleting old cache:', cacheName);
return caches.delete(cacheName);
}
})
);
}),
// Take control of all clients immediately
clients.claim()
])
);
});
// Fetch event - ALWAYS network first, no caching for HTML/JS/CSS
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url);
// Skip non-GET requests
if (event.request.method !== 'GET') {
return;
}
// API calls - always network, no cache
if (url.pathname.includes('/api/')) {
event.respondWith(fetch(event.request));
return;
}
// HTML, JS, CSS - always fetch fresh from network
// This ensures PWA always loads latest version
event.respondWith(
fetch(event.request)
.then(response => {
return response;
})
.catch(() => {
// Only fallback to cache if network fails
return caches.match(event.request);
})
);
});

View File

@@ -23,12 +23,13 @@
border-radius: 16px; border-radius: 16px;
overflow: hidden; overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.1);
background: var(--surface-card);
} }
.login-header { .login-header {
text-align: center; text-align: center;
padding: 2rem 2rem 1rem 2rem; padding: 2rem 2rem 1rem 2rem;
background: white; background: var(--surface-card);
} }
.login-title { .login-title {
@@ -46,6 +47,7 @@
.login-form { .login-form {
padding: 0 2rem 2rem 2rem; padding: 0 2rem 2rem 2rem;
background: var(--surface-card);
} }
.login-button { .login-button {

View File

@@ -107,8 +107,8 @@
} }
.btn-success:hover { .btn-success:hover {
background: #047857; background: var(--green-700);
border-color: #047857; border-color: var(--green-700);
} }
.btn-warning { .btn-warning {
@@ -118,8 +118,8 @@
} }
.btn-warning:hover { .btn-warning:hover {
background: #b45309; background: var(--yellow-700);
border-color: #b45309; border-color: var(--yellow-700);
} }
.btn-error { .btn-error {
@@ -129,8 +129,8 @@
} }
.btn-error:hover { .btn-error:hover {
background: #b91c1c; background: var(--red-700);
border-color: #b91c1c; border-color: var(--red-700);
} }
/* Button Shapes */ /* Button Shapes */

View File

@@ -1,9 +1,9 @@
/* Card Components - ROA2WEB */ /* Card Components - ROA2WEB */
/* Base Card Styles */ /* Base Card Styles - Dark mode compatible */
.card { .card {
background: var(--color-bg); background: var(--surface-card) !important;
border: 1px solid var(--color-border); border: 1px solid var(--surface-border);
border-radius: var(--card-radius); border-radius: var(--card-radius);
box-shadow: var(--shadow-sm); box-shadow: var(--shadow-sm);
transition: all var(--transition-fast); transition: all var(--transition-fast);
@@ -17,8 +17,8 @@
.card-header { .card-header {
padding: var(--space-lg); padding: var(--space-lg);
border-bottom: 1px solid var(--color-border); border-bottom: 1px solid var(--surface-border);
background: var(--color-bg-secondary); background: var(--surface-hover);
} }
.card-body { .card-body {
@@ -27,8 +27,8 @@
.card-footer { .card-footer {
padding: var(--space-lg); padding: var(--space-lg);
border-top: 1px solid var(--color-border); border-top: 1px solid var(--surface-border);
background: var(--color-bg-secondary); background: var(--surface-hover);
} }
/* Card Variants */ /* Card Variants */
@@ -57,10 +57,10 @@
transform: translateY(-2px); transform: translateY(-2px);
} }
/* Stats Cards */ /* Stats Cards - Dark mode compatible */
.stats-card { .stats-card {
background: var(--color-bg); background: var(--surface-card) !important;
border: 1px solid var(--color-border); border: 1px solid var(--surface-border);
border-radius: var(--card-radius); border-radius: var(--card-radius);
padding: var(--space-lg); padding: var(--space-lg);
text-align: center; text-align: center;
@@ -124,8 +124,8 @@
/* KPI Cards */ /* KPI Cards */
.kpi-card { .kpi-card {
background: var(--color-bg); background: var(--surface-card);
border: 1px solid var(--color-border); border: 1px solid var(--surface-border);
border-radius: var(--card-radius); border-radius: var(--card-radius);
padding: var(--space-lg); padding: var(--space-lg);
display: flex; display: flex;
@@ -172,8 +172,8 @@
/* Action Cards */ /* Action Cards */
.action-card { .action-card {
background: var(--color-bg); background: var(--surface-card);
border: 1px solid var(--color-border); border: 1px solid var(--surface-border);
border-radius: var(--card-radius); border-radius: var(--card-radius);
padding: var(--space-lg); padding: var(--space-lg);
cursor: pointer; cursor: pointer;
@@ -209,8 +209,8 @@
/* Status Cards */ /* Status Cards */
.status-card { .status-card {
background: var(--color-bg); background: var(--surface-card);
border-left: 4px solid var(--color-border); border-left: 4px solid var(--surface-border);
border-radius: var(--card-radius); border-radius: var(--card-radius);
padding: var(--space-md); padding: var(--space-md);
box-shadow: var(--shadow-sm); box-shadow: var(--shadow-sm);
@@ -218,22 +218,22 @@
.status-card.success { .status-card.success {
border-left-color: var(--color-success); border-left-color: var(--color-success);
background: #f0fdf4; background: var(--green-50);
} }
.status-card.warning { .status-card.warning {
border-left-color: var(--color-warning); border-left-color: var(--color-warning);
background: #fffbeb; background: var(--yellow-50);
} }
.status-card.error { .status-card.error {
border-left-color: var(--color-error); border-left-color: var(--color-error);
background: #fef2f2; background: var(--red-50);
} }
.status-card.info { .status-card.info {
border-left-color: var(--color-info); border-left-color: var(--color-info);
background: #f0f9ff; background: var(--blue-50);
} }
/* Company Banner Card */ /* Company Banner Card */
@@ -262,8 +262,8 @@
/* Dashboard V2 Mini Cards */ /* Dashboard V2 Mini Cards */
.mini-card { .mini-card {
background: var(--color-bg); background: var(--surface-card);
border: 1px solid var(--color-border); border: 1px solid var(--surface-border);
border-radius: var(--radius-sm); border-radius: var(--radius-sm);
padding: var(--space-sm); padding: var(--space-sm);
text-align: center; text-align: center;
@@ -298,17 +298,17 @@
/* Heatmap Colors for Mini Cards */ /* Heatmap Colors for Mini Cards */
.mini-card.heat-low { .mini-card.heat-low {
background: #f0fdf4; background: var(--green-50);
border-color: var(--color-success); border-color: var(--color-success);
} }
.mini-card.heat-medium { .mini-card.heat-medium {
background: #fffbeb; background: var(--yellow-50);
border-color: var(--color-warning); border-color: var(--color-warning);
} }
.mini-card.heat-high { .mini-card.heat-high {
background: #fef2f2; background: var(--red-50);
border-color: var(--color-error); border-color: var(--color-error);
} }
@@ -366,8 +366,8 @@
/* ===== Dashboard Metric Cards ===== */ /* ===== Dashboard Metric Cards ===== */
.metric-card { .metric-card {
background: var(--color-bg); background: var(--surface-card);
border: 1px solid var(--color-border); border: 1px solid var(--surface-border);
border-radius: var(--card-radius); border-radius: var(--card-radius);
padding: var(--card-padding, 1.5rem); padding: var(--card-padding, 1.5rem);
transition: all var(--transition-fast); transition: all var(--transition-fast);

View File

@@ -278,7 +278,7 @@
/* Heat Map Colors for Mini Cards */ /* Heat Map Colors for Mini Cards */
.mini-stat-card.heat-low { .mini-stat-card.heat-low {
background: #f0fdf4; background: var(--green-50);
border-color: var(--color-success); border-color: var(--color-success);
} }
@@ -287,7 +287,7 @@
} }
.mini-stat-card.heat-medium { .mini-stat-card.heat-medium {
background: #fffbeb; background: var(--yellow-50);
border-color: var(--color-warning); border-color: var(--color-warning);
} }
@@ -296,7 +296,7 @@
} }
.mini-stat-card.heat-high { .mini-stat-card.heat-high {
background: #fef2f2; background: var(--red-50);
border-color: var(--color-error); border-color: var(--color-error);
} }
@@ -332,30 +332,30 @@
/* Stats Card Variants */ /* Stats Card Variants */
.stats-card.clients { .stats-card.clients {
border-left-color: #3b82f6; border-left-color: var(--blue-500);
} }
.stats-card.clients .stats-card-header i { .stats-card.clients .stats-card-header i {
color: #3b82f6; color: var(--blue-500);
background: #eff6ff; background: var(--blue-50);
} }
.stats-card.suppliers { .stats-card.suppliers {
border-left-color: #f59e0b; border-left-color: var(--yellow-500);
} }
.stats-card.suppliers .stats-card-header i { .stats-card.suppliers .stats-card-header i {
color: #f59e0b; color: var(--yellow-600);
background: #fffbeb; background: var(--yellow-50);
} }
.stats-card.treasury { .stats-card.treasury {
border-left-color: #10b981; border-left-color: var(--green-500);
} }
.stats-card.treasury .stats-card-header i { .stats-card.treasury .stats-card-header i {
color: #10b981; color: var(--green-500);
background: #ecfdf5; background: var(--green-50);
} }
/* Responsive Adjustments */ /* Responsive Adjustments */

View File

@@ -74,6 +74,74 @@
--surface-900: #0f172a; --surface-900: #0f172a;
--surface-950: #020617; --surface-950: #020617;
/* Semantic surface tokens (PrimeVue-style) - CRITICAL for dark mode */
--surface-card: #ffffff;
--surface-ground: #f8fafc;
--surface-section: #ffffff;
--surface-hover: #f1f5f9;
--surface-border: #e2e8f0;
--surface-overlay: #ffffff;
/* Primary color scale */
--primary-50: #eff6ff;
--primary-100: #dbeafe;
--primary-200: #bfdbfe;
--primary-300: #93c5fd;
--primary-400: #60a5fa;
--primary-500: #3b82f6;
--primary-600: #2563eb;
--primary-700: #1d4ed8;
--primary-800: #1e40af;
--primary-900: #1e3a8a;
/* Green color scale (success) */
--green-50: #f0fdf4;
--green-100: #dcfce7;
--green-200: #bbf7d0;
--green-300: #86efac;
--green-400: #4ade80;
--green-500: #22c55e;
--green-600: #16a34a;
--green-700: #15803d;
--green-800: #166534;
--green-900: #14532d;
/* Yellow color scale (warning) */
--yellow-50: #fefce8;
--yellow-100: #fef9c3;
--yellow-200: #fef08a;
--yellow-300: #fde047;
--yellow-400: #facc15;
--yellow-500: #eab308;
--yellow-600: #ca8a04;
--yellow-700: #a16207;
--yellow-800: #854d0e;
--yellow-900: #713f12;
/* Blue color scale (info) */
--blue-50: #eff6ff;
--blue-100: #dbeafe;
--blue-200: #bfdbfe;
--blue-300: #93c5fd;
--blue-400: #60a5fa;
--blue-500: #3b82f6;
--blue-600: #2563eb;
--blue-700: #1d4ed8;
--blue-800: #1e40af;
--blue-900: #1e3a8a;
/* Gray color scale */
--gray-50: #f9fafb;
--gray-100: #f3f4f6;
--gray-200: #e5e7eb;
--gray-300: #d1d5db;
--gray-400: #9ca3af;
--gray-500: #6b7280;
--gray-600: #4b5563;
--gray-700: #374151;
--gray-800: #1f2937;
--gray-900: #111827;
/* Red color palette for errors */ /* Red color palette for errors */
--red-50: #fef2f2; --red-50: #fef2f2;
--red-100: #fee2e2; --red-100: #fee2e2;
@@ -152,33 +220,154 @@
--breakpoint-wide: 1400px; --breakpoint-wide: 1400px;
} }
/* Dark mode support (for future enhancement) */ /* Manual dark theme via data-theme attribute (highest priority) */
@media (prefers-color-scheme: dark) { [data-theme="dark"] {
:root { /* Text colors - inverted for dark mode */
--color-text: #f9fafb; --color-text: #f9fafb;
--color-text-secondary: #d1d5db; --color-text-secondary: #d1d5db;
--color-text-muted: #9ca3af; --color-text-muted: #9ca3af;
--text-color: #f9fafb;
--text-color-secondary: #d1d5db;
/* Background colors */
--color-bg: #111827; --color-bg: #111827;
--color-bg-secondary: #1f2937; --color-bg-secondary: #1f2937;
--color-bg-muted: #374151; --color-bg-muted: #374151;
/* Border colors */
--color-border: #374151; --color-border: #374151;
--color-border-light: #4b5563; --color-border-light: #4b5563;
--color-border-dark: #6b7280; --color-border-dark: #6b7280;
/* Surface colors for dark mode */ /* Surface numeric scale - inverted for dark mode */
--surface-0: #ffffff; --surface-0: #0f172a;
--surface-50: #020617; --surface-50: #1e293b;
--surface-100: #0f172a; --surface-100: #334155;
--surface-200: #1e293b; --surface-200: #475569;
--surface-300: #334155; --surface-300: #64748b;
--surface-400: #475569; --surface-400: #94a3b8;
--surface-500: #64748b; --surface-500: #cbd5e1;
--surface-600: #94a3b8; --surface-600: #e2e8f0;
--surface-700: #cbd5e1; --surface-700: #f1f5f9;
--surface-800: #e2e8f0; --surface-800: #f8fafc;
--surface-900: #f1f5f9; --surface-900: #ffffff;
--surface-950: #f8fafc; --surface-950: #ffffff;
/* Red colors remain the same in dark mode */ /* Semantic surface tokens - CRITICAL for dark mode components */
--surface-card: #1e293b;
--surface-ground: #0f172a;
--surface-section: #1e293b;
--surface-hover: #334155;
--surface-border: #475569;
--surface-overlay: #1e293b;
/* Gray scale - inverted for dark mode */
--gray-50: #111827;
--gray-100: #1f2937;
--gray-200: #374151;
--gray-300: #4b5563;
--gray-400: #6b7280;
--gray-500: #9ca3af;
--gray-600: #d1d5db;
--gray-700: #e5e7eb;
--gray-800: #f3f4f6;
--gray-900: #f9fafb;
/* Status colors - slightly adjusted for dark backgrounds */
--green-50: #052e16;
--green-100: #14532d;
--green-800: #86efac;
--green-900: #bbf7d0;
--yellow-50: #422006;
--yellow-100: #713f12;
--yellow-800: #fde047;
--yellow-900: #fef08a;
--red-50: #450a0a;
--red-100: #7f1d1d;
--red-800: #fca5a5;
--red-900: #fecaca;
--blue-50: #1e3a8a;
--blue-100: #1e40af;
--blue-800: #93c5fd;
--blue-900: #bfdbfe;
}
/* System preference dark mode (fallback when no manual theme set) */
@media (prefers-color-scheme: dark) {
:root:not([data-theme]) {
/* Text colors - inverted for dark mode */
--color-text: #f9fafb;
--color-text-secondary: #d1d5db;
--color-text-muted: #9ca3af;
--text-color: #f9fafb;
--text-color-secondary: #d1d5db;
/* Background colors */
--color-bg: #111827;
--color-bg-secondary: #1f2937;
--color-bg-muted: #374151;
/* Border colors */
--color-border: #374151;
--color-border-light: #4b5563;
--color-border-dark: #6b7280;
/* Surface numeric scale - inverted for dark mode */
--surface-0: #0f172a;
--surface-50: #1e293b;
--surface-100: #334155;
--surface-200: #475569;
--surface-300: #64748b;
--surface-400: #94a3b8;
--surface-500: #cbd5e1;
--surface-600: #e2e8f0;
--surface-700: #f1f5f9;
--surface-800: #f8fafc;
--surface-900: #ffffff;
--surface-950: #ffffff;
/* Semantic surface tokens - CRITICAL for dark mode components */
--surface-card: #1e293b;
--surface-ground: #0f172a;
--surface-section: #1e293b;
--surface-hover: #334155;
--surface-border: #475569;
--surface-overlay: #1e293b;
/* Gray scale - inverted for dark mode */
--gray-50: #111827;
--gray-100: #1f2937;
--gray-200: #374151;
--gray-300: #4b5563;
--gray-400: #6b7280;
--gray-500: #9ca3af;
--gray-600: #d1d5db;
--gray-700: #e5e7eb;
--gray-800: #f3f4f6;
--gray-900: #f9fafb;
/* Status colors - slightly adjusted for dark backgrounds */
--green-50: #052e16;
--green-100: #14532d;
--green-800: #86efac;
--green-900: #bbf7d0;
--yellow-50: #422006;
--yellow-100: #713f12;
--yellow-800: #fde047;
--yellow-900: #fef08a;
--red-50: #450a0a;
--red-100: #7f1d1d;
--red-800: #fca5a5;
--red-900: #fecaca;
--blue-50: #1e3a8a;
--blue-100: #1e40af;
--blue-800: #93c5fd;
--blue-900: #bfdbfe;
} }
} }

View File

@@ -4,59 +4,27 @@
/* @import '../../../../../shared/frontend/styles/layout/header.css'; */ /* @import '../../../../../shared/frontend/styles/layout/header.css'; */
/* @import '../../../../../shared/frontend/styles/layout/navigation.css'; */ /* @import '../../../../../shared/frontend/styles/layout/navigation.css'; */
/* Note: Color variables are defined in core/variables.css with dark mode support */
/* This file only overrides layout-specific variables for data-entry module */
:root { :root {
/* Layout variables */ /* Layout variables (data-entry specific) */
--header-height: 60px; --header-height: 60px;
--sidebar-width: 280px; --sidebar-width: 280px;
--z-header: 100; --z-header: 100;
--z-modal: 1000; --z-modal: 1000;
--z-modal-backdrop: 999; --z-modal-backdrop: 999;
}
/* Shadows */ /* Dark mode overrides for Data Entry module */
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1); /* These must be here because data-entry.css is loaded before main.css */
[data-theme="dark"] {
/* Transitions */ --surface-card: #1e293b;
--transition-fast: 0.15s ease; --surface-ground: #0f172a;
--transition-normal: 0.3s ease; --surface-border: #475569;
--surface-hover: #334155;
/* Typography */ --text-color: #f9fafb;
--font-semibold: 600; --text-color-secondary: #94a3b8;
--font-medium: 500;
--text-xs: 12px;
--text-sm: 14px;
--text-base: 16px;
--text-lg: 18px;
/* Radius */
--radius-md: 6px;
--radius-full: 9999px;
/* Spacing */
--space-xs: 4px;
--space-sm: 8px;
--space-md: 12px;
--space-lg: 24px;
/* Colors - Primary palette (matching reports-app) */
--color-primary: #2563eb;
--color-primary-dark: #1d4ed8;
--color-primary-light: #3b82f6;
/* Compatibility aliases */
--primary-color: var(--color-primary);
--text-color: #111827;
--text-color-secondary: #6b7280;
/* Surface colors for PrimeVue */
--surface-0: #ffffff;
--surface-50: #f8fafc;
--surface-100: #f1f5f9;
--surface-200: #e2e8f0;
/* Red color palette for errors */
--red-50: #fef2f2;
--red-200: #fecaca;
--red-800: #991b1b;
} }
* { * {
@@ -70,13 +38,14 @@ body {
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
/* Card styles */ /* Card styles - Dark mode compatible */
.roa-card { .roa-card {
background: white; background: var(--surface-card) !important;
border-radius: 12px; border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
padding: 1.5rem; padding: 1.5rem;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
border: 1px solid var(--surface-border);
} }
.roa-card-header { .roa-card-header {
@@ -85,13 +54,13 @@ body {
align-items: center; align-items: center;
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
padding-bottom: 1rem; padding-bottom: 1rem;
border-bottom: 1px solid #eee; border-bottom: 1px solid var(--surface-border);
} }
.roa-card-title { .roa-card-title {
font-size: 1.25rem; font-size: 1.25rem;
font-weight: 600; font-weight: 600;
color: #333; color: var(--text-color);
margin: 0; margin: 0;
display: flex; display: flex;
align-items: center; align-items: center;
@@ -113,7 +82,7 @@ body {
.form-field label { .form-field label {
font-weight: 500; font-weight: 500;
color: #555; color: var(--text-color-secondary);
font-size: 0.9rem; font-size: 0.9rem;
} }
@@ -138,28 +107,28 @@ body {
} }
.status-draft { .status-draft {
background-color: #e3f2fd; background-color: var(--blue-50);
color: #1976d2; color: var(--blue-600);
} }
.status-pending { .status-pending {
background-color: #fff3e0; background-color: var(--yellow-50);
color: #f57c00; color: var(--yellow-700);
} }
.status-approved { .status-approved {
background-color: #e8f5e9; background-color: var(--green-50);
color: #388e3c; color: var(--green-600);
} }
.status-rejected { .status-rejected {
background-color: #ffebee; background-color: var(--red-50);
color: #d32f2f; color: var(--red-600);
} }
.status-synced { .status-synced {
background-color: #e0f2f1; background-color: var(--green-50);
color: #00796b; color: var(--green-700);
} }
/* Table styles */ /* Table styles */
@@ -174,8 +143,8 @@ body {
} }
.p-datatable .p-datatable-thead > tr > th { .p-datatable .p-datatable-thead > tr > th {
background: #f8f9fa; background: var(--surface-hover);
color: #495057; color: var(--text-color);
font-weight: 600; font-weight: 600;
} }
@@ -188,7 +157,7 @@ body {
/* Upload area */ /* Upload area */
.upload-area { .upload-area {
border: 2px dashed #ddd; border: 2px dashed var(--surface-border);
border-radius: 12px; border-radius: 12px;
padding: 2rem; padding: 2rem;
text-align: center; text-align: center;
@@ -197,13 +166,13 @@ body {
} }
.upload-area:hover { .upload-area:hover {
border-color: #667eea; border-color: var(--primary-500);
background-color: #f8f9ff; background-color: var(--surface-hover);
} }
.upload-area.has-files { .upload-area.has-files {
border-style: solid; border-style: solid;
border-color: #667eea; border-color: var(--primary-500);
} }
/* Image preview */ /* Image preview */
@@ -244,21 +213,21 @@ body {
.entries-table td { .entries-table td {
padding: 0.75rem; padding: 0.75rem;
text-align: left; text-align: left;
border-bottom: 1px solid #eee; border-bottom: 1px solid var(--surface-border);
} }
.entries-table th { .entries-table th {
background: #f8f9fa; background: var(--surface-hover);
font-weight: 600; font-weight: 600;
color: #495057; color: var(--text-color);
} }
.entries-table .debit { .entries-table .debit {
color: #d32f2f; color: var(--red-600);
} }
.entries-table .credit { .entries-table .credit {
color: #388e3c; color: var(--green-600);
} }
/* Stats cards */ /* Stats cards */
@@ -270,7 +239,7 @@ body {
} }
.stat-card { .stat-card {
background: white; background: var(--surface-card);
border-radius: 12px; border-radius: 12px;
padding: 1.25rem; padding: 1.25rem;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
@@ -280,11 +249,11 @@ body {
.stat-card .stat-value { .stat-card .stat-value {
font-size: 2rem; font-size: 2rem;
font-weight: 700; font-weight: 700;
color: #333; color: var(--text-color);
} }
.stat-card .stat-label { .stat-card .stat-label {
color: #666; color: var(--text-color-secondary);
font-size: 0.9rem; font-size: 0.9rem;
margin-top: 0.25rem; margin-top: 0.25rem;
} }
@@ -301,12 +270,12 @@ body {
.empty-state { .empty-state {
text-align: center; text-align: center;
padding: 3rem; padding: 3rem;
color: #666; color: var(--text-color-secondary);
} }
.empty-state i { .empty-state i {
font-size: 4rem; font-size: 4rem;
color: #ddd; color: var(--surface-border);
margin-bottom: 1rem; margin-bottom: 1rem;
} }

View File

@@ -80,17 +80,17 @@
transition: background-color var(--transition-fast) !important; transition: background-color var(--transition-fast) !important;
} }
/* DataTable Striped Rows - Global Pattern */ /* DataTable Striped Rows - Global Pattern (Dark Mode Compatible) */
.p-datatable .p-datatable-tbody > tr:nth-child(odd) { .p-datatable .p-datatable-tbody > tr:nth-child(odd) {
background-color: #ffffff !important; background-color: var(--surface-card) !important;
} }
.p-datatable .p-datatable-tbody > tr:nth-child(even) { .p-datatable .p-datatable-tbody > tr:nth-child(even) {
background-color: #f8f9fa !important; background-color: var(--surface-ground) !important;
} }
.p-datatable .p-datatable-tbody > tr:hover { .p-datatable .p-datatable-tbody > tr:hover {
background-color: #e3f2fd !important; background-color: var(--surface-hover) !important;
cursor: pointer; cursor: pointer;
} }
@@ -112,6 +112,7 @@
/* ===== Card ===== */ /* ===== Card ===== */
.p-card { .p-card {
background: var(--surface-card) !important;
box-shadow: var(--shadow-sm) !important; box-shadow: var(--shadow-sm) !important;
border: 1px solid var(--color-border) !important; border: 1px solid var(--color-border) !important;
border-radius: var(--card-radius, 8px) !important; border-radius: var(--card-radius, 8px) !important;
@@ -125,6 +126,11 @@
.p-card .p-card-body { .p-card .p-card-body {
padding: var(--space-lg) !important; padding: var(--space-lg) !important;
background: var(--surface-card) !important;
}
.p-card .p-card-content {
background: var(--surface-card) !important;
} }
/* ===== Mobile Optimizations ===== */ /* ===== Mobile Optimizations ===== */
@@ -136,3 +142,104 @@
font-size: 16px !important; /* Prevent iOS zoom */ font-size: 16px !important; /* Prevent iOS zoom */
} }
} }
/* ===== Compact DataTable (ReceiptsListView) ===== */
.compact-table .p-datatable-tbody > tr > td {
padding: 0.5rem 0.5rem;
font-size: 0.875rem;
}
.compact-table .p-datatable-thead > tr > th {
padding: 0.5rem 0.5rem;
font-size: 0.8rem;
}
.compact-table .p-paginator {
padding: 0.5rem;
}
.compact-table .p-datatable-wrapper {
overflow-x: auto;
}
/* ===== Receipt Form InputNumber (ReceiptCreateView) ===== */
.value-item-total .p-inputnumber {
width: 100% !important;
max-width: 130px !important;
}
.value-item-total .p-inputnumber-input {
width: 100% !important;
}
.payment-method-item .p-inputnumber {
width: 100% !important;
max-width: 110px !important;
}
.payment-method-item .p-inputnumber-input {
width: 100% !important;
}
.dropdown-payment {
width: 100% !important;
max-width: 190px !important;
}
.dropdown-payment .p-dropdown {
width: 100% !important;
}
.input-tva {
width: 110px !important;
}
.input-tva .p-inputnumber-input {
width: 110px !important;
}
/* ===== Receipt Form Mobile (ReceiptCreateView) ===== */
@media (max-width: 768px) {
.form-field .p-dropdown,
.form-field .p-autocomplete,
.form-field .p-calendar {
width: 100% !important;
}
}
/* ===== OCR Preview Mobile (OCRPreview) ===== */
@media (max-width: 768px) {
.preview-actions .p-button {
width: 100%;
}
}
/* ===== OCR Engine Selector (OCRUploadZone) ===== */
.engine-selector .p-dropdown-label {
padding: 0.5rem 0.75rem !important;
font-size: 0.875rem;
color: var(--text-color-secondary);
}
.engine-selector .p-dropdown-trigger {
width: 2rem !important;
}
/* ===== Table Card Striped Rows (InvoicesView) ===== */
/* Note: Uses design tokens for dark mode compatibility */
.table-card .p-datatable .p-datatable-tbody > tr {
transition: background-color 0.2s ease;
}
.table-card .p-datatable .p-datatable-tbody > tr:nth-child(odd) {
background-color: var(--surface-card, #ffffff);
}
.table-card .p-datatable .p-datatable-tbody > tr:nth-child(even) {
background-color: var(--surface-ground, #f8f9fa);
}
.table-card .p-datatable .p-datatable-tbody > tr:hover {
background-color: var(--surface-hover, #e3f2fd) !important;
cursor: pointer;
}

View File

@@ -2,7 +2,7 @@
<div class="ocr-preview"> <div class="ocr-preview">
<div class="preview-header"> <div class="preview-header">
<div class="header-left"> <div class="header-left">
<i class="pi pi-check-circle" style="color: #22c55e; font-size: 1.25rem;"></i> <i class="pi pi-check-circle" style="color: var(--green-500); font-size: 1.25rem;"></i>
<span class="title">Date extrase din imagine</span> <span class="title">Date extrase din imagine</span>
</div> </div>
<div class="header-right"> <div class="header-right">
@@ -319,8 +319,8 @@ const formatProcessingTime = (ms) => {
<style scoped> <style scoped>
.ocr-preview { .ocr-preview {
background: #f0fdf4; background: var(--green-50);
border: 1px solid #86efac; border: 1px solid var(--green-300);
border-radius: 12px; border-radius: 12px;
margin: 1rem 0; margin: 1rem 0;
overflow: hidden; overflow: hidden;
@@ -331,8 +331,8 @@ const formatProcessingTime = (ms) => {
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
background: #dcfce7; background: var(--green-100);
border-bottom: 1px solid #86efac; border-bottom: 1px solid var(--green-300);
} }
.header-left { .header-left {
@@ -343,7 +343,7 @@ const formatProcessingTime = (ms) => {
.title { .title {
font-weight: 600; font-weight: 600;
color: #166534; color: var(--green-800);
} }
.header-right { .header-right {
@@ -357,11 +357,11 @@ const formatProcessingTime = (ms) => {
align-items: center; align-items: center;
gap: 0.5rem; gap: 0.5rem;
font-size: 0.85rem; font-size: 0.85rem;
color: #166534; color: var(--green-800);
} }
.collapse-btn { .collapse-btn {
color: #166534 !important; color: var(--green-800) !important;
} }
.preview-content { .preview-content {
@@ -374,7 +374,7 @@ const formatProcessingTime = (ms) => {
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
background: rgba(255, 255, 255, 0.5); background: rgba(255, 255, 255, 0.5);
border-radius: 8px; border-radius: 8px;
border: 1px solid #bbf7d0; border: 1px solid var(--green-200);
} }
.ocr-section:last-of-type { .ocr-section:last-of-type {
@@ -384,16 +384,16 @@ const formatProcessingTime = (ms) => {
.ocr-section-title { .ocr-section-title {
font-size: 0.7rem; font-size: 0.7rem;
font-weight: 700; font-weight: 700;
color: #166534; color: var(--green-800);
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.08em; letter-spacing: 0.08em;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
padding-bottom: 0.35rem; padding-bottom: 0.35rem;
border-bottom: 1px dashed #86efac; border-bottom: 1px dashed var(--green-300);
} }
.ocr-section-content { .ocr-section-content {
color: #1e293b; color: var(--text-color);
} }
/* FURNIZOR section */ /* FURNIZOR section */
@@ -407,13 +407,13 @@ const formatProcessingTime = (ms) => {
.vendor-cui { .vendor-cui {
font-size: 0.85rem; font-size: 0.85rem;
color: #475569; color: var(--text-color-secondary);
margin-top: 0.15rem; margin-top: 0.15rem;
} }
.vendor-address { .vendor-address {
font-size: 0.8rem; font-size: 0.8rem;
color: #64748b; color: var(--text-color-secondary);
margin-top: 0.15rem; margin-top: 0.15rem;
} }
@@ -428,19 +428,19 @@ const formatProcessingTime = (ms) => {
.client-cui { .client-cui {
font-size: 0.85rem; font-size: 0.85rem;
color: #475569; color: var(--text-color-secondary);
margin-top: 0.15rem; margin-top: 0.15rem;
} }
.client-address { .client-address {
font-size: 0.8rem; font-size: 0.8rem;
color: #64748b; color: var(--text-color-secondary);
margin-top: 0.15rem; margin-top: 0.15rem;
} }
/* Placeholder when no data extracted */ /* Placeholder when no data extracted */
.no-data { .no-data {
color: #9ca3af; color: var(--text-color-secondary);
font-style: italic; font-style: italic;
} }
@@ -454,20 +454,20 @@ const formatProcessingTime = (ms) => {
.doc-number { .doc-number {
font-weight: 500; font-weight: 500;
color: #334155; color: var(--text-color);
} }
.doc-date { .doc-date {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.35rem; gap: 0.35rem;
color: #475569; color: var(--text-color-secondary);
font-size: 0.9rem; font-size: 0.9rem;
} }
.doc-date .pi-calendar { .doc-date .pi-calendar {
font-size: 0.85rem; font-size: 0.85rem;
color: #64748b; color: var(--text-color-secondary);
} }
/* Unified values table (TOTAL + Payment + TVA) */ /* Unified values table (TOTAL + Payment + TVA) */
@@ -486,13 +486,13 @@ const formatProcessingTime = (ms) => {
.value-label { .value-label {
font-weight: 500; font-weight: 500;
color: #374151; color: var(--text-color);
} }
.value-amount { .value-amount {
font-weight: 600; font-weight: 600;
margin-left: auto; margin-left: auto;
color: #1f2937; color: var(--text-color);
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.5rem; gap: 0.5rem;
@@ -501,13 +501,13 @@ const formatProcessingTime = (ms) => {
.value-row.total-row { .value-row.total-row {
margin-top: 0.35rem; margin-top: 0.35rem;
padding-top: 0.5rem; padding-top: 0.5rem;
border-top: 1px dashed #86efac; border-top: 1px dashed var(--green-300);
} }
/* Items count - subtle, at bottom of values section */ /* Items count - subtle, at bottom of values section */
.items-count-inline { .items-count-inline {
font-size: 0.8rem; font-size: 0.8rem;
color: #64748b; color: var(--text-color-secondary);
text-align: right; text-align: right;
padding-top: 0.35rem; padding-top: 0.35rem;
} }
@@ -519,7 +519,7 @@ const formatProcessingTime = (ms) => {
/* Warning row for calculated total */ /* Warning row for calculated total */
.value-row.warning-row { .value-row.warning-row {
background: #fef3c7; background: var(--yellow-100);
border-radius: 6px; border-radius: 6px;
padding: 0.5rem 0.75rem; padding: 0.5rem 0.75rem;
margin: 0.25rem 0; margin: 0.25rem 0;
@@ -529,22 +529,22 @@ const formatProcessingTime = (ms) => {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.5rem; gap: 0.5rem;
color: #92400e; color: var(--yellow-800);
} }
.warning-icon { .warning-icon {
color: #f59e0b; color: var(--yellow-500);
font-size: 0.9rem; font-size: 0.9rem;
} }
.value-amount.calculated { .value-amount.calculated {
color: #92400e; color: var(--yellow-800);
} }
.value-amount .hint { .value-amount .hint {
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 400; font-weight: 400;
color: #a3a3a3; color: var(--text-color-secondary);
} }
/* Validation warnings */ /* Validation warnings */
@@ -554,14 +554,14 @@ const formatProcessingTime = (ms) => {
gap: 0.5rem; gap: 0.5rem;
padding: 0.5rem 0.75rem; padding: 0.5rem 0.75rem;
margin: 0.25rem 0; margin: 0.25rem 0;
background: #fee2e2; background: var(--red-100);
border-radius: 6px; border-radius: 6px;
font-size: 0.8rem; font-size: 0.8rem;
color: #991b1b; color: var(--red-800);
} }
.validation-warning i { .validation-warning i {
color: #dc2626; color: var(--red-600);
font-size: 0.9rem; font-size: 0.9rem;
} }
@@ -572,21 +572,21 @@ const formatProcessingTime = (ms) => {
gap: 0.5rem; gap: 0.5rem;
padding: 0.5rem 0.75rem; padding: 0.5rem 0.75rem;
margin: 0.25rem 0; margin: 0.25rem 0;
background: #dbeafe; background: var(--blue-100);
border-radius: 6px; border-radius: 6px;
font-size: 0.8rem; font-size: 0.8rem;
color: #1e40af; color: var(--blue-700);
} }
.validation-info i { .validation-info i {
color: #2563eb; color: var(--blue-600);
font-size: 0.9rem; font-size: 0.9rem;
} }
.raw-text-section { .raw-text-section {
margin-top: 1rem; margin-top: 1rem;
padding-top: 1rem; padding-top: 1rem;
border-top: 1px dashed #86efac; border-top: 1px dashed var(--green-300);
} }
.raw-text-header { .raw-text-header {
@@ -607,44 +607,44 @@ const formatProcessingTime = (ms) => {
} }
.ocr-engine-badge.paddleocr { .ocr-engine-badge.paddleocr {
background: #dbeafe; background: var(--blue-100);
color: #1e40af; color: var(--blue-700);
} }
.ocr-engine-badge.tesseract { .ocr-engine-badge.tesseract {
background: #fef3c7; background: var(--yellow-100);
color: #92400e; color: var(--yellow-800);
} }
.ocr-engine-badge.fast { .ocr-engine-badge.fast {
background: #dcfce7; background: var(--green-100);
color: #166534; color: var(--green-800);
} }
.ocr-engine-badge.adaptive { .ocr-engine-badge.adaptive {
background: #dbeafe; background: var(--blue-100);
color: #1e40af; color: var(--blue-700);
} }
.ocr-engine-badge.full { .ocr-engine-badge.full {
background: #fef3c7; background: var(--yellow-100);
color: #92400e; color: var(--yellow-800);
} }
/* docTR engine styles */ /* docTR engine styles */
.ocr-engine-badge.doctr { .ocr-engine-badge.doctr {
background: #ede9fe; background: var(--purple-100);
color: #5b21b6; color: var(--purple-700);
} }
.ocr-engine-badge.doctr-fast { .ocr-engine-badge.doctr-fast {
background: #d1fae5; background: var(--teal-100);
color: #047857; color: var(--teal-700);
} }
.ocr-engine-badge.doctr-adaptive { .ocr-engine-badge.doctr-adaptive {
background: #e0e7ff; background: var(--indigo-100);
color: #3730a3; color: var(--indigo-700);
} }
.ocr-message-badge { .ocr-message-badge {
@@ -655,18 +655,18 @@ const formatProcessingTime = (ms) => {
border-radius: 4px; border-radius: 4px;
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 500; font-weight: 500;
background: #f1f5f9; background: var(--surface-100);
color: #475569; color: var(--text-color-secondary);
} }
.ocr-message-badge.fast-mode { .ocr-message-badge.fast-mode {
background: #dcfce7; background: var(--green-100);
color: #166534; color: var(--green-800);
} }
.ocr-message-badge.full-pipeline { .ocr-message-badge.full-pipeline {
background: #fef3c7; background: var(--yellow-100);
color: #92400e; color: var(--yellow-800);
} }
.ocr-time-badge { .ocr-time-badge {
@@ -677,15 +677,15 @@ const formatProcessingTime = (ms) => {
border-radius: 4px; border-radius: 4px;
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 600; font-weight: 600;
background: #e0e7ff; background: var(--indigo-100);
color: #3730a3; color: var(--indigo-700);
} }
.raw-text { .raw-text {
margin-top: 0.5rem; margin-top: 0.5rem;
padding: 0.75rem; padding: 0.75rem;
background: white; background: var(--surface-card);
border: 1px solid #e2e8f0; border: 1px solid var(--surface-border);
border-radius: 8px; border-radius: 8px;
max-height: 200px; max-height: 200px;
overflow: auto; overflow: auto;
@@ -696,7 +696,7 @@ const formatProcessingTime = (ms) => {
font-size: 0.75rem; font-size: 0.75rem;
white-space: pre-wrap; white-space: pre-wrap;
word-break: break-word; word-break: break-word;
color: #475569; color: var(--text-color-secondary);
} }
.preview-actions { .preview-actions {
@@ -704,8 +704,8 @@ const formatProcessingTime = (ms) => {
justify-content: flex-end; justify-content: flex-end;
gap: 0.75rem; gap: 0.75rem;
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
background: #f8fafc; background: var(--surface-ground);
border-top: 1px solid #e2e8f0; border-top: 1px solid var(--surface-border);
} }
@media (max-width: 640px) { @media (max-width: 640px) {
@@ -722,9 +722,6 @@ const formatProcessingTime = (ms) => {
.preview-actions { .preview-actions {
flex-direction: column; flex-direction: column;
} }
/* PrimeVue button width moved to vendor/primevue-overrides.css */
.preview-actions :deep(.p-button) {
width: 100%;
}
} }
</style> </style>

View File

@@ -26,13 +26,13 @@
</div> </div>
<div v-else-if="selectedFile" class="file-selected-state"> <div v-else-if="selectedFile" class="file-selected-state">
<i class="pi pi-check-circle" style="font-size: 1.75rem; color: #22c55e;"></i> <i class="pi pi-check-circle" style="font-size: 1.75rem; color: var(--green-500);"></i>
<p class="file-name">{{ selectedFile.name }}</p> <p class="file-name">{{ selectedFile.name }}</p>
<p class="file-size">{{ formatFileSize(selectedFile.size) }}</p> <p class="file-size">{{ formatFileSize(selectedFile.size) }}</p>
</div> </div>
<div v-else class="empty-state"> <div v-else class="empty-state">
<i class="pi pi-camera" style="font-size: 2rem; color: #667eea;"></i> <i class="pi pi-camera" style="font-size: 2rem; color: var(--primary-500);"></i>
<p class="main-text"> <p class="main-text">
<span v-if="isDragging">Elibereaza pentru a incarca</span> <span v-if="isDragging">Elibereaza pentru a incarca</span>
<span v-else>Trage poza bonului aici sau click pentru a selecta</span> <span v-else>Trage poza bonului aici sau click pentru a selecta</span>
@@ -436,29 +436,29 @@ defineExpose({ reset, processOCR })
} }
.upload-dropzone { .upload-dropzone {
border: 2px dashed #cbd5e1; border: 2px dashed var(--surface-border);
border-radius: 10px; border-radius: 10px;
padding: 1rem 1.25rem; padding: 1rem 1.25rem;
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
transition: all 0.3s ease; transition: all 0.3s ease;
background: #f8fafc; background: var(--surface-ground);
} }
.upload-dropzone:hover { .upload-dropzone:hover {
border-color: #667eea; border-color: var(--primary-500);
background: #f1f5f9; background: var(--surface-hover);
} }
.upload-dropzone.dragging { .upload-dropzone.dragging {
border-color: #667eea; border-color: var(--primary-500);
background: #eef2ff; background: var(--primary-50);
transform: scale(1.02); transform: scale(1.02);
} }
.upload-dropzone.processing { .upload-dropzone.processing {
cursor: default; cursor: default;
background: #fefefe; background: var(--surface-card);
} }
.hidden-input { .hidden-input {
@@ -475,13 +475,13 @@ defineExpose({ reset, processOCR })
.main-text { .main-text {
font-size: 0.9rem; font-size: 0.9rem;
color: #475569; color: var(--text-color-secondary);
margin: 0.25rem 0; margin: 0.25rem 0;
} }
.sub-text { .sub-text {
font-size: 0.8rem; font-size: 0.8rem;
color: #94a3b8; color: var(--text-color-secondary);
margin: 0; margin: 0;
} }
@@ -496,14 +496,14 @@ defineExpose({ reset, processOCR })
.file-name { .file-name {
font-weight: 600; font-weight: 600;
font-size: 0.9rem; font-size: 0.9rem;
color: #1e293b; color: var(--text-color);
margin: 0.25rem 0 0 0; margin: 0.25rem 0 0 0;
word-break: break-all; word-break: break-all;
} }
.file-size { .file-size {
font-size: 0.8rem; font-size: 0.8rem;
color: #64748b; color: var(--text-color-secondary);
margin: 0; margin: 0;
} }
@@ -517,21 +517,11 @@ defineExpose({ reset, processOCR })
flex-wrap: wrap; flex-wrap: wrap;
} }
/* Engine selector dropdown */ /* Engine selector dropdown - PrimeVue styles in vendor/primevue-overrides.css */
.engine-selector { .engine-selector {
min-width: 180px; min-width: 180px;
} }
.engine-selector:deep(.p-dropdown-label) {
padding: 0.5rem 0.75rem !important;
font-size: 0.875rem;
color: #475569;
}
.engine-selector:deep(.p-dropdown-trigger) {
width: 2rem !important;
}
/* Engine dropdown option with description */ /* Engine dropdown option with description */
.engine-option { .engine-option {
display: flex; display: flex;
@@ -543,12 +533,12 @@ defineExpose({ reset, processOCR })
.engine-label { .engine-label {
font-weight: 500; font-weight: 500;
font-size: 0.875rem; font-size: 0.875rem;
color: #1e293b; color: var(--text-color);
} }
.engine-desc { .engine-desc {
font-size: 0.75rem; font-size: 0.75rem;
color: #64748b; color: var(--text-color-secondary);
} }
/* Processing state */ /* Processing state */
@@ -561,14 +551,14 @@ defineExpose({ reset, processOCR })
.processing-text { .processing-text {
font-size: 1rem; font-size: 1rem;
color: #475569; color: var(--text-color-secondary);
margin: 0.5rem 0 0 0; margin: 0.5rem 0 0 0;
font-weight: 500; font-weight: 500;
} }
.processing-subtext { .processing-subtext {
font-size: 0.85rem; font-size: 0.85rem;
color: #94a3b8; color: var(--text-color-secondary);
margin: 0; margin: 0;
} }
</style> </style>

View File

@@ -12,7 +12,7 @@
<!-- Empty State --> <!-- Empty State -->
<div v-else-if="!hasData" class="empty-state"> <div v-else-if="!hasData" class="empty-state">
<i class="pi pi-chart-bar" style="font-size: 3rem; color: #94a3b8;"></i> <i class="pi pi-chart-bar empty-state-icon"></i>
<h3>Nu exista date</h3> <h3>Nu exista date</h3>
<p>Procesati imagini cu OCR pentru a vedea statisticile.</p> <p>Procesati imagini cu OCR pentru a vedea statisticile.</p>
</div> </div>
@@ -163,28 +163,28 @@
<h3>Statistici Generale</h3> <h3>Statistici Generale</h3>
<div class="stats-grid"> <div class="stats-grid">
<div class="stat-box"> <div class="stat-box">
<i class="pi pi-file" style="color: #667eea;"></i> <i class="pi pi-file stat-icon stat-icon--primary"></i>
<div class="stat-info"> <div class="stat-info">
<span class="stat-number">{{ stats.total_jobs || 0 }}</span> <span class="stat-number">{{ stats.total_jobs || 0 }}</span>
<span class="stat-desc">Total joburi</span> <span class="stat-desc">Total joburi</span>
</div> </div>
</div> </div>
<div class="stat-box"> <div class="stat-box">
<i class="pi pi-check-circle" style="color: #22c55e;"></i> <i class="pi pi-check-circle stat-icon stat-icon--success"></i>
<div class="stat-info"> <div class="stat-info">
<span class="stat-number">{{ stats.successful_jobs || 0 }}</span> <span class="stat-number">{{ stats.successful_jobs || 0 }}</span>
<span class="stat-desc">Procesate cu succes</span> <span class="stat-desc">Procesate cu succes</span>
</div> </div>
</div> </div>
<div class="stat-box"> <div class="stat-box">
<i class="pi pi-times-circle" style="color: #ef4444;"></i> <i class="pi pi-times-circle stat-icon stat-icon--error"></i>
<div class="stat-info"> <div class="stat-info">
<span class="stat-number">{{ stats.failed_jobs || 0 }}</span> <span class="stat-number">{{ stats.failed_jobs || 0 }}</span>
<span class="stat-desc">Esuate</span> <span class="stat-desc">Esuate</span>
</div> </div>
</div> </div>
<div class="stat-box"> <div class="stat-box">
<i class="pi pi-clock" style="color: #f59e0b;"></i> <i class="pi pi-clock stat-icon stat-icon--warning"></i>
<div class="stat-info"> <div class="stat-info">
<span class="stat-number">{{ formatTime(stats.avg_processing_time_ms) }}</span> <span class="stat-number">{{ formatTime(stats.avg_processing_time_ms) }}</span>
<span class="stat-desc">Timp mediu total</span> <span class="stat-desc">Timp mediu total</span>
@@ -823,7 +823,7 @@ onBeforeUnmount(() => {
.page-header h1 { .page-header h1 {
font-size: 1.5rem; font-size: 1.5rem;
font-weight: 600; font-weight: 600;
color: #1e293b; color: var(--text-color);
margin: 0; margin: 0;
} }
@@ -968,11 +968,11 @@ onBeforeUnmount(() => {
/* Section Card */ /* Section Card */
.section-card { .section-card {
background: white; background: var(--surface-card);
border-radius: 12px; border-radius: 12px;
padding: 1.25rem; padding: 1.25rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); box-shadow: var(--shadow-sm);
border: 1px solid #e2e8f0; border: 1px solid var(--surface-border);
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
@@ -1115,11 +1115,11 @@ onBeforeUnmount(() => {
} }
.chart-card { .chart-card {
background: white; background: var(--surface-card);
border-radius: 12px; border-radius: 12px;
padding: 1.25rem; padding: 1.25rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); box-shadow: var(--shadow-sm);
border: 1px solid #e2e8f0; border: 1px solid var(--surface-border);
} }
.chart-card h3 { .chart-card h3 {
@@ -1136,11 +1136,11 @@ onBeforeUnmount(() => {
/* Stats Overview */ /* Stats Overview */
.stats-overview { .stats-overview {
background: white; background: var(--surface-card);
border-radius: 12px; border-radius: 12px;
padding: 1.25rem; padding: 1.25rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); box-shadow: var(--shadow-sm);
border: 1px solid #e2e8f0; border: 1px solid var(--surface-border);
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
@@ -1162,7 +1162,7 @@ onBeforeUnmount(() => {
align-items: center; align-items: center;
gap: 0.75rem; gap: 0.75rem;
padding: 0.75rem; padding: 0.75rem;
background: #f8fafc; background: var(--surface-ground);
border-radius: 8px; border-radius: 8px;
} }
@@ -1178,12 +1178,12 @@ onBeforeUnmount(() => {
.stat-number { .stat-number {
font-size: 1.25rem; font-size: 1.25rem;
font-weight: 600; font-weight: 600;
color: #1e293b; color: var(--text-color);
} }
.stat-desc { .stat-desc {
font-size: 0.8rem; font-size: 0.8rem;
color: #64748b; color: var(--text-color-secondary);
} }
/* Engine Badges */ /* Engine Badges */
@@ -1292,6 +1292,32 @@ onBeforeUnmount(() => {
min-width: 100%; min-width: 100%;
} }
} }
/* Dark mode compatible icon styles */
.empty-state-icon {
font-size: 3rem;
color: var(--text-color-secondary);
}
.stat-icon {
font-size: 1.5rem;
}
.stat-icon--primary {
color: var(--primary-500, #667eea);
}
.stat-icon--success {
color: var(--green-500, #22c55e);
}
.stat-icon--error {
color: var(--red-500, #ef4444);
}
.stat-icon--warning {
color: var(--yellow-500, #f59e0b);
}
</style> </style>
<!-- Non-scoped styles for PrimeVue overrides --> <!-- Non-scoped styles for PrimeVue overrides -->

View File

@@ -159,7 +159,7 @@
v-else-if="att.mime_type?.startsWith('image/') && !attachmentBlobUrls[att.id]" v-else-if="att.mime_type?.startsWith('image/') && !attachmentBlobUrls[att.id]"
class="image-placeholder" class="image-placeholder"
> >
<i class="pi pi-image" style="font-size: 2rem; color: #94a3b8;"></i> <i class="pi pi-image" style="font-size: 2rem; color: var(--text-color-secondary);"></i>
<span>{{ att.filename }}</span> <span>{{ att.filename }}</span>
</div> </div>
<div v-else class="pdf-preview"> <div v-else class="pdf-preview">
@@ -2071,16 +2071,16 @@ const submitForReview = async () => {
gap: 0.5rem; gap: 0.5rem;
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
border-radius: 8px; border-radius: 8px;
background: #fef3c7; background: var(--yellow-100);
border: 1px solid #fbbf24; border: 1px solid var(--yellow-400);
color: #92400e; color: var(--yellow-800);
font-size: 0.875rem; font-size: 0.875rem;
} }
.validation-banner.warning { .validation-banner.warning {
background: #fee2e2; background: var(--red-100);
border-color: #f87171; border-color: var(--red-400);
color: #991b1b; color: var(--red-800);
} }
.validation-banner i { .validation-banner i {
@@ -2167,8 +2167,8 @@ const submitForReview = async () => {
align-items: center; align-items: center;
gap: 0.5rem; gap: 0.5rem;
padding: 0.6rem 1rem; padding: 0.6rem 1rem;
background: #dcfce7; background: var(--green-100);
border: 1px solid #86efac; border: 1px solid var(--green-300);
border-radius: 8px; border-radius: 8px;
cursor: pointer; cursor: pointer;
margin-top: 0.75rem; margin-top: 0.75rem;
@@ -2176,23 +2176,23 @@ const submitForReview = async () => {
} }
.ocr-applied-banner:hover { .ocr-applied-banner:hover {
background: #bbf7d0; background: var(--green-200);
} }
.ocr-applied-banner .pi-check-circle { .ocr-applied-banner .pi-check-circle {
color: #22c55e; color: var(--green-500);
font-size: 1.1rem; font-size: 1.1rem;
} }
.ocr-applied-banner span { .ocr-applied-banner span {
flex: 1; flex: 1;
font-weight: 500; font-weight: 500;
color: #166534; color: var(--green-800);
font-size: 0.9rem; font-size: 0.9rem;
} }
.ocr-applied-banner .pi-chevron-down { .ocr-applied-banner .pi-chevron-down {
color: #166534; color: var(--green-800);
font-size: 0.8rem; font-size: 0.8rem;
} }
@@ -2222,11 +2222,11 @@ const submitForReview = async () => {
/* Pending files header */ /* Pending files header */
.pending-files-header { .pending-files-header {
font-size: 0.85rem; font-size: 0.85rem;
color: #64748b; color: var(--text-color-secondary);
font-weight: 500; font-weight: 500;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
padding-bottom: 0.35rem; padding-bottom: 0.35rem;
border-bottom: 1px dashed #cbd5e1; border-bottom: 1px dashed var(--surface-border);
} }
.radio-group { .radio-group {
@@ -2246,7 +2246,7 @@ const submitForReview = async () => {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
height: 100%; height: 100%;
background: #f5f5f5; background: var(--surface-100);
padding: 1rem; padding: 1rem;
} }
@@ -2270,20 +2270,20 @@ const submitForReview = async () => {
align-items: center; align-items: center;
gap: 0.75rem; gap: 0.75rem;
padding: 0.5rem 0.75rem; padding: 0.5rem 0.75rem;
background: #f8fafc; background: var(--surface-ground);
border: 1px solid #e2e8f0; border: 1px solid var(--surface-border);
border-radius: 8px; border-radius: 8px;
} }
.selected-file-item i { .selected-file-item i {
color: #667eea; color: var(--primary-500);
font-size: 1.25rem; font-size: 1.25rem;
} }
.selected-file-item .file-name { .selected-file-item .file-name {
flex: 1; flex: 1;
font-weight: 500; font-weight: 500;
color: #1e293b; color: var(--text-color);
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@@ -2291,15 +2291,15 @@ const submitForReview = async () => {
.selected-file-item .file-size { .selected-file-item .file-size {
font-size: 0.85rem; font-size: 0.85rem;
color: #64748b; color: var(--text-color-secondary);
} }
/* Extra details section (TVA, items, address) */ /* Extra details section (TVA, items, address) */
.extra-details-section { .extra-details-section {
margin-top: 1rem; margin-top: 1rem;
padding: 0.75rem; padding: 0.75rem;
background: #f0f9ff; background: var(--blue-50);
border: 1px solid #bae6fd; border: 1px solid var(--blue-200);
border-radius: 8px; border-radius: 8px;
} }
@@ -2309,7 +2309,7 @@ const submitForReview = async () => {
gap: 0.5rem; gap: 0.5rem;
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
margin-top: 0; margin-top: 0;
color: #0284c7; color: var(--blue-600);
font-size: 0.95rem; font-size: 0.95rem;
} }
@@ -2329,13 +2329,13 @@ const submitForReview = async () => {
.tva-row.total { .tva-row.total {
margin-top: 0.5rem; margin-top: 0.5rem;
padding-top: 0.5rem; padding-top: 0.5rem;
border-top: 1px dashed #0284c7; border-top: 1px dashed var(--blue-600);
} }
.tva-label { .tva-label {
min-width: 150px; min-width: 150px;
font-weight: 500; font-weight: 500;
color: #334155; color: var(--text-color);
} }
.tva-input { .tva-input {
@@ -2344,7 +2344,7 @@ const submitForReview = async () => {
.tva-value { .tva-value {
font-weight: 600; font-weight: 600;
color: #0284c7; color: var(--blue-600);
} }
/* Supplier warning */ /* Supplier warning */
@@ -2353,7 +2353,7 @@ const submitForReview = async () => {
align-items: center; align-items: center;
gap: 0.25rem; gap: 0.25rem;
margin-top: 0.25rem; margin-top: 0.25rem;
color: #f59e0b; color: var(--yellow-500);
} }
/* Supplier selected indicator */ /* Supplier selected indicator */
@@ -2362,7 +2362,7 @@ const submitForReview = async () => {
align-items: center; align-items: center;
gap: 0.25rem; gap: 0.25rem;
margin-top: 0.25rem; margin-top: 0.25rem;
color: #22c55e; color: var(--green-500);
font-weight: 500; font-weight: 500;
} }
@@ -2371,7 +2371,7 @@ const submitForReview = async () => {
display: block; display: block;
margin-top: 0.25rem; margin-top: 0.25rem;
font-size: 0.8rem; font-size: 0.8rem;
color: #64748b; color: var(--text-color-secondary);
} }
/* Payment methods display */ /* Payment methods display */
@@ -2407,7 +2407,7 @@ const submitForReview = async () => {
display: block; display: block;
margin-bottom: 0.25rem; margin-bottom: 0.25rem;
font-weight: 500; font-weight: 500;
color: #334155; color: var(--text-color);
} }
/* ======================================== /* ========================================
@@ -2416,7 +2416,7 @@ const submitForReview = async () => {
.form-section { .form-section {
padding: 0.6rem 0; padding: 0.6rem 0;
border-bottom: 1px solid #e2e8f0; border-bottom: 1px solid var(--surface-border);
} }
.form-section:last-of-type { .form-section:last-of-type {
@@ -2426,7 +2426,7 @@ const submitForReview = async () => {
.form-section-title { .form-section-title {
font-size: 0.7rem; font-size: 0.7rem;
font-weight: 600; font-weight: 600;
color: #64748b; color: var(--text-color-secondary);
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.05em; letter-spacing: 0.05em;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
@@ -2489,7 +2489,7 @@ const submitForReview = async () => {
.direction-header .field-label { .direction-header .field-label {
font-size: 0.85rem; font-size: 0.85rem;
font-weight: 500; font-weight: 500;
color: #475569; color: var(--text-color-secondary);
} }
.auto-tag { .auto-tag {
@@ -2519,7 +2519,7 @@ const submitForReview = async () => {
align-items: center; align-items: center;
gap: 0.25rem; gap: 0.25rem;
font-size: 0.75rem; font-size: 0.75rem;
color: #22c55e; color: var(--green-500);
font-weight: 500; font-weight: 500;
white-space: nowrap; white-space: nowrap;
} }
@@ -2544,7 +2544,7 @@ const submitForReview = async () => {
.tva-label-compact { .tva-label-compact {
min-width: 80px; min-width: 80px;
font-size: 0.85rem; font-size: 0.85rem;
color: #475569; color: var(--text-color-secondary);
} }
.tva-input-compact { .tva-input-compact {
@@ -2554,12 +2554,12 @@ const submitForReview = async () => {
.tva-edit-row.tva-total-row { .tva-edit-row.tva-total-row {
margin-top: 0.35rem; margin-top: 0.35rem;
padding-top: 0.35rem; padding-top: 0.35rem;
border-top: 1px dashed #cbd5e1; border-top: 1px dashed var(--surface-border);
} }
.tva-total-value { .tva-total-value {
font-weight: 600; font-weight: 600;
color: #0284c7; color: var(--blue-600);
font-size: 0.9rem; font-size: 0.9rem;
} }
@@ -2578,7 +2578,7 @@ const submitForReview = async () => {
/* Form groups with subtle borders */ /* Form groups with subtle borders */
.form-group { .form-group {
padding: 0.75rem 0; padding: 0.75rem 0;
border-bottom: 1px solid #e2e8f0; border-bottom: 1px solid var(--surface-border);
} }
.form-group:first-child { .form-group:first-child {
@@ -2606,24 +2606,24 @@ const submitForReview = async () => {
gap: 0.4rem; gap: 0.4rem;
padding: 0.3rem 0.5rem; padding: 0.3rem 0.5rem;
margin-top: 0.4rem; margin-top: 0.4rem;
background: #f8fafc; background: var(--surface-ground);
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
transition: background 0.15s; transition: background 0.15s;
} }
.address-collapsed:hover { .address-collapsed:hover {
background: #f1f5f9; background: var(--surface-hover);
} }
.address-collapsed i { .address-collapsed i {
font-size: 0.7rem; font-size: 0.7rem;
color: #64748b; color: var(--text-color-secondary);
} }
.address-preview { .address-preview {
font-size: 0.75rem; font-size: 0.75rem;
color: #64748b; color: var(--text-color-secondary);
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@@ -2634,7 +2634,7 @@ const submitForReview = async () => {
.address-expanded { .address-expanded {
margin-top: 0.4rem; margin-top: 0.4rem;
padding: 0.4rem; padding: 0.4rem;
background: #f8fafc; background: var(--surface-ground);
border-radius: 4px; border-radius: 4px;
} }
@@ -2660,7 +2660,7 @@ const submitForReview = async () => {
======================================== */ ======================================== */
.values-section { .values-section {
background: #f8fafc; background: var(--surface-ground);
border-radius: 6px; border-radius: 6px;
padding: 0.5rem 0.75rem; padding: 0.5rem 0.75rem;
} }
@@ -2702,55 +2702,23 @@ const submitForReview = async () => {
.value-item label { .value-item label {
font-size: 0.75rem; font-size: 0.75rem;
font-weight: 500; font-weight: 500;
color: #64748b; color: var(--text-color-secondary);
text-transform: uppercase; text-transform: uppercase;
} }
/* Force InputNumber to respect container width */ /* Force InputNumber to respect container width - PrimeVue styles in vendor/primevue-overrides.css */
.input-compact { .input-compact {
width: 100% !important; width: 100% !important;
max-width: 130px !important; max-width: 130px !important;
} }
:deep(.value-item-total .p-inputnumber) {
width: 100% !important;
max-width: 130px !important;
}
:deep(.value-item-total .p-inputnumber-input) {
width: 100% !important;
}
:deep(.payment-method-item .p-inputnumber) {
width: 100% !important;
max-width: 110px !important;
}
:deep(.payment-method-item .p-inputnumber-input) {
width: 100% !important;
}
.dropdown-payment {
width: 100% !important;
max-width: 190px !important;
}
:deep(.dropdown-payment) {
width: 100% !important;
max-width: 190px !important;
}
:deep(.dropdown-payment .p-dropdown) {
width: 100% !important;
}
.payment-method-value { .payment-method-value {
display: inline-block; display: inline-block;
font-size: 0.95rem; font-size: 0.95rem;
font-weight: 500; font-weight: 500;
color: #334155; color: var(--text-color);
background: #f8fafc; background: var(--surface-ground);
border: 1px solid #e2e8f0; border: 1px solid var(--surface-border);
padding: 0.5rem 0.75rem; padding: 0.5rem 0.75rem;
border-radius: 6px; border-radius: 6px;
min-width: 90px; min-width: 90px;
@@ -2761,7 +2729,7 @@ const submitForReview = async () => {
.tva-compact { .tva-compact {
margin-top: 0.5rem; margin-top: 0.5rem;
padding-top: 0.5rem; padding-top: 0.5rem;
border-top: 1px dashed #cbd5e1; border-top: 1px dashed var(--surface-border);
} }
.tva-item { .tva-item {
@@ -2771,14 +2739,7 @@ const submitForReview = async () => {
.input-tva { .input-tva {
width: 110px !important; width: 110px !important;
} }
/* PrimeVue input-tva styles moved to vendor/primevue-overrides.css */
:deep(.input-tva) {
width: 110px !important;
}
:deep(.input-tva .p-inputnumber-input) {
width: 110px !important;
}
.tva-total-item { .tva-total-item {
padding-left: 0.5rem; padding-left: 0.5rem;
@@ -2788,9 +2749,9 @@ const submitForReview = async () => {
display: inline-block; display: inline-block;
font-size: 0.95rem; font-size: 0.95rem;
font-weight: 500; font-weight: 500;
color: #334155; color: var(--text-color);
background: #f8fafc; background: var(--surface-ground);
border: 1px solid #e2e8f0; border: 1px solid var(--surface-border);
padding: 0.5rem 0.75rem; padding: 0.5rem 0.75rem;
border-radius: 6px; border-radius: 6px;
min-width: 90px; min-width: 90px;
@@ -2800,12 +2761,12 @@ const submitForReview = async () => {
/* Small labels for secondary fields */ /* Small labels for secondary fields */
.label-small { .label-small {
font-size: 0.8rem !important; font-size: 0.8rem !important;
color: #64748b !important; color: var(--text-color-secondary) !important;
} }
/* Muted labels for optional fields */ /* Muted labels for optional fields */
.label-muted { .label-muted {
color: #94a3b8 !important; color: var(--text-color-secondary) !important;
} }
/* Small inputs */ /* Small inputs */
@@ -2845,7 +2806,7 @@ const submitForReview = async () => {
align-items: flex-end; align-items: flex-end;
padding: 0.5rem 0; padding: 0.5rem 0;
margin-top: 0.5rem; margin-top: 0.5rem;
background: #f8fafc; background: var(--surface-ground);
border-radius: 4px; border-radius: 4px;
padding: 0.5rem; padding: 0.5rem;
} }
@@ -2866,14 +2827,14 @@ const submitForReview = async () => {
.tva-total-inline { .tva-total-inline {
font-weight: 500; font-weight: 500;
color: #334155; color: var(--text-color);
font-size: 0.95rem; font-size: 0.95rem;
padding: 0.5rem 0; padding: 0.5rem 0;
} }
/* Optional fields row */ /* Optional fields row */
.optional-fields { .optional-fields {
background: #f8fafc; background: var(--surface-ground);
padding: 0.5rem; padding: 0.5rem;
border-radius: 4px; border-radius: 4px;
margin-top: 0.4rem; margin-top: 0.4rem;
@@ -2958,20 +2919,15 @@ const submitForReview = async () => {
min-width: 80px; min-width: 80px;
} }
/* Dropdowns and inputs full width in their containers */ /* Dropdowns and inputs full width - styles in vendor/primevue-overrides.css */
.form-field :deep(.p-dropdown),
.form-field :deep(.p-autocomplete),
.form-field :deep(.p-calendar) {
width: 100% !important;
}
/* Action buttons on mobile - not fixed, flow with content */ /* Action buttons on mobile - not fixed, flow with content */
.action-buttons-top { .action-buttons-top {
position: static; position: static;
width: 100%; width: 100%;
padding: 1rem; padding: 1rem;
background: #f8fafc; background: var(--surface-ground);
border-top: 1px solid #e2e8f0; border-top: 1px solid var(--surface-border);
margin-top: 1rem; margin-top: 1rem;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@@ -3006,28 +2962,28 @@ const submitForReview = async () => {
} }
.status-draft { .status-draft {
background: #f1f5f9; background: var(--surface-100);
color: #475569; color: var(--text-color-secondary);
} }
.status-pending { .status-pending {
background: #fef3c7; background: var(--yellow-100);
color: #92400e; color: var(--yellow-800);
} }
.status-approved { .status-approved {
background: #dcfce7; background: var(--green-100);
color: #166534; color: var(--green-800);
} }
.status-rejected { .status-rejected {
background: #fee2e2; background: var(--red-100);
color: #991b1b; color: var(--red-800);
} }
.status-synced { .status-synced {
background: #dbeafe; background: var(--blue-100);
color: #1e40af; color: var(--blue-700);
} }
/* Rejection Alert */ /* Rejection Alert */
@@ -3035,14 +2991,14 @@ const submitForReview = async () => {
display: flex; display: flex;
gap: 1rem; gap: 1rem;
padding: 1rem; padding: 1rem;
background: #fff3e0; background: var(--orange-100);
border-radius: 8px; border-radius: 8px;
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.rejection-alert i { .rejection-alert i {
font-size: 1.5rem; font-size: 1.5rem;
color: #f57c00; color: var(--orange-500);
} }
.rejection-alert p { .rejection-alert p {
@@ -3064,8 +3020,8 @@ const submitForReview = async () => {
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
border: 1px solid #e2e8f0; border: 1px solid var(--surface-border);
background: #f8fafc; background: var(--surface-ground);
} }
.attachment-image { .attachment-image {
@@ -3085,12 +3041,12 @@ const submitForReview = async () => {
.pdf-attachment i { .pdf-attachment i {
font-size: 2.5rem; font-size: 2.5rem;
color: #dc2626; color: var(--red-600);
} }
.pdf-attachment span { .pdf-attachment span {
font-size: 0.85rem; font-size: 0.85rem;
color: #475569; color: var(--text-color-secondary);
text-align: center; text-align: center;
word-break: break-word; word-break: break-word;
} }
@@ -3122,8 +3078,8 @@ const submitForReview = async () => {
position: relative; position: relative;
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow: hidden;
border: 1px solid #e2e8f0; border: 1px solid var(--surface-border);
background: #f8fafc; background: var(--surface-ground);
} }
.image-preview-item img { .image-preview-item img {
@@ -3141,8 +3097,8 @@ const submitForReview = async () => {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 0.5rem; gap: 0.5rem;
background: #f1f5f9; background: var(--surface-hover);
color: #94a3b8; color: var(--text-color-secondary);
font-size: 0.75rem; font-size: 0.75rem;
text-align: center; text-align: center;
padding: 0.5rem; padding: 0.5rem;
@@ -3160,7 +3116,7 @@ const submitForReview = async () => {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 2rem; padding: 2rem;
color: #94a3b8; color: var(--text-color-secondary);
text-align: center; text-align: center;
} }
@@ -3177,7 +3133,7 @@ const submitForReview = async () => {
.entries-section { .entries-section {
margin-top: 1.5rem; margin-top: 1.5rem;
padding: 1rem; padding: 1rem;
background: #f8fafc; background: var(--surface-ground);
border-radius: 8px; border-radius: 8px;
} }
@@ -3186,7 +3142,7 @@ const submitForReview = async () => {
align-items: center; align-items: center;
gap: 0.5rem; gap: 0.5rem;
margin: 0 0 1rem 0; margin: 0 0 1rem 0;
color: #334155; color: var(--text-color);
} }
.entries-table-container { .entries-table-container {
@@ -3203,25 +3159,25 @@ const submitForReview = async () => {
.entries-table td { .entries-table td {
padding: 0.75rem; padding: 0.75rem;
text-align: left; text-align: left;
border-bottom: 1px solid #e2e8f0; border-bottom: 1px solid var(--surface-border);
} }
.entries-table th { .entries-table th {
background: #f1f5f9; background: var(--surface-hover);
font-weight: 600; font-weight: 600;
color: #475569; color: var(--text-color-secondary);
} }
.entries-table tbody tr:hover { .entries-table tbody tr:hover {
background: #f8fafc; background: var(--surface-ground);
} }
.entries-table .debit { .entries-table .debit {
color: #dc2626; color: var(--red-600);
} }
.entries-table .credit { .entries-table .credit {
color: #16a34a; color: var(--green-600);
} }
.entries-table tfoot td { .entries-table tfoot td {
@@ -3235,9 +3191,9 @@ const submitForReview = async () => {
gap: 0.5rem; gap: 0.5rem;
margin-top: 1rem; margin-top: 1rem;
padding: 0.75rem; padding: 0.75rem;
background: #fff3e0; background: var(--orange-100);
border-radius: 8px; border-radius: 8px;
color: #f57c00; color: var(--orange-500);
} }
</style> </style>

View File

@@ -436,11 +436,12 @@
</template> </template>
<script setup> <script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue' import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useToast } from 'primevue/usetoast' import { useToast } from 'primevue/usetoast'
import { useConfirm } from 'primevue/useconfirm' import { useConfirm } from 'primevue/useconfirm'
import { useReceiptsStore } from '@data-entry/stores/receiptsStore' import { useReceiptsStore } from '@data-entry/stores/receiptsStore'
import { useCompanyStore } from '@data-entry/stores/sharedStores'
import Menu from 'primevue/menu' import Menu from 'primevue/menu'
import Dialog from 'primevue/dialog' import Dialog from 'primevue/dialog'
import Textarea from 'primevue/textarea' import Textarea from 'primevue/textarea'
@@ -450,6 +451,7 @@ const router = useRouter()
const toast = useToast() const toast = useToast()
const confirm = useConfirm() const confirm = useConfirm()
const store = useReceiptsStore() const store = useReceiptsStore()
const companyStore = useCompanyStore()
// Rejection dialog state // Rejection dialog state
const rejectDialogVisible = ref(false) const rejectDialogVisible = ref(false)
@@ -477,6 +479,29 @@ onUnmounted(() => {
window.removeEventListener('resize', handleResize) window.removeEventListener('resize', handleResize)
}) })
// Watch for company changes and reload data
watch(
() => companyStore.selectedCompany,
async (newCompany, oldCompany) => {
// Skip initial load (handled by onMounted) and same company
if (!oldCompany || !newCompany) return
if (newCompany.id_firma === oldCompany.id_firma) return
console.log('[ReceiptsList] Company changed, reloading data...')
// Reset filters and pagination when company changes
filters.value = {
status: null,
search: '',
direction: null,
dateFrom: null,
dateTo: null,
}
store.clearFilters()
await store.fetchStats()
await store.fetchReceipts()
}
)
// Mobile context menu // Mobile context menu
const menuRef = ref() const menuRef = ref()
const selectedReceipt = ref(null) const selectedReceipt = ref(null)
@@ -966,7 +991,7 @@ const approveSelected = async () => {
padding: 0.25rem 0.5rem; padding: 0.25rem 0.5rem;
border-radius: 4px; border-radius: 4px;
font-size: 0.8rem; font-size: 0.8rem;
color: #64748b; color: var(--text-color-secondary);
cursor: pointer; cursor: pointer;
transition: all 0.15s ease; transition: all 0.15s ease;
white-space: nowrap; white-space: nowrap;
@@ -974,12 +999,12 @@ const approveSelected = async () => {
} }
.status-chip:hover { .status-chip:hover {
background: #f1f5f9; background: var(--surface-hover);
} }
.status-chip.active { .status-chip.active {
background: #e2e8f0; background: var(--surface-200);
color: #1e293b; color: var(--text-color);
font-weight: 600; font-weight: 600;
position: relative; position: relative;
} }
@@ -991,22 +1016,22 @@ const approveSelected = async () => {
left: 0; left: 0;
right: 0; right: 0;
height: 3px; height: 3px;
background: #1e293b; background: var(--text-color);
border-radius: 2px; border-radius: 2px;
} }
/* Color accents for active status */ /* Color accents for active status */
.status-chip.status-draft.active { background: #dbeafe; color: #1d4ed8; } .status-chip.status-draft.active { background: var(--blue-100); color: var(--blue-700); }
.status-chip.status-draft.active::after { background: #1d4ed8; } .status-chip.status-draft.active::after { background: var(--blue-700); }
.status-chip.status-pending.active { background: #fef3c7; color: #b45309; } .status-chip.status-pending.active { background: var(--yellow-100); color: var(--yellow-700); }
.status-chip.status-pending.active::after { background: #d97706; } .status-chip.status-pending.active::after { background: var(--yellow-600); }
.status-chip.status-approved.active { background: #dcfce7; color: #15803d; } .status-chip.status-approved.active { background: var(--green-100); color: var(--green-700); }
.status-chip.status-approved.active::after { background: #16a34a; } .status-chip.status-approved.active::after { background: var(--green-600); }
.status-chip.status-rejected.active { background: #fee2e2; color: #b91c1c; } .status-chip.status-rejected.active { background: var(--red-100); color: var(--red-700); }
.status-chip.status-rejected.active::after { background: #dc2626; } .status-chip.status-rejected.active::after { background: var(--red-600); }
/* Search and Filters Row */ /* Search and Filters Row */
.filters-row { .filters-row {
@@ -1016,7 +1041,7 @@ const approveSelected = async () => {
flex-wrap: wrap; flex-wrap: wrap;
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
padding-bottom: 0.75rem; padding-bottom: 0.75rem;
border-bottom: 1px solid #e2e8f0; border-bottom: 1px solid var(--surface-border);
} }
.filter-search { .filter-search {
@@ -1042,20 +1067,7 @@ const approveSelected = async () => {
margin-left: auto; margin-left: auto;
} }
/* Compact DataTable */ /* Compact DataTable - styles moved to vendor/primevue-overrides.css */
.compact-table :deep(.p-datatable-tbody > tr > td) {
padding: 0.5rem 0.5rem;
font-size: 0.875rem;
}
.compact-table :deep(.p-datatable-thead > tr > th) {
padding: 0.5rem 0.5rem;
font-size: 0.8rem;
}
.compact-table :deep(.p-paginator) {
padding: 0.5rem;
}
/* Action buttons - always on same line */ /* Action buttons - always on same line */
.compact-table .button-group { .compact-table .button-group {
@@ -1072,17 +1084,17 @@ const approveSelected = async () => {
} }
.receipt-card { .receipt-card {
background: white; background: var(--surface-card);
border-radius: 8px; border-radius: 8px;
padding: 0.75rem; padding: 0.75rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); box-shadow: var(--shadow-sm);
cursor: pointer; cursor: pointer;
border: 1px solid #e2e8f0; border: 1px solid var(--surface-border);
transition: all 0.2s ease; transition: all 0.2s ease;
} }
.receipt-card:active { .receipt-card:active {
background: #f8fafc; background: var(--surface-ground);
transform: scale(0.99); transform: scale(0.99);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
} }
@@ -1105,7 +1117,7 @@ const approveSelected = async () => {
.card-row-1 .partner { .card-row-1 .partner {
font-weight: 600; font-weight: 600;
font-size: 0.875rem; font-size: 0.875rem;
color: #1e293b; color: var(--text-color);
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@@ -1113,7 +1125,7 @@ const approveSelected = async () => {
.card-row-1 .cui { .card-row-1 .cui {
font-size: 0.7rem; font-size: 0.7rem;
color: #94a3b8; color: var(--text-color-secondary);
} }
.card-row-1 .amount-block { .card-row-1 .amount-block {
@@ -1126,21 +1138,21 @@ const approveSelected = async () => {
.card-row-1 .amount { .card-row-1 .amount {
font-weight: 700; font-weight: 700;
font-size: 0.875rem; font-size: 0.875rem;
color: #0f172a; color: var(--text-color);
} }
.card-row-1 .amount-detail { .card-row-1 .amount-detail {
font-size: 0.875rem; font-size: 0.875rem;
font-weight: 400; font-weight: 400;
color: #64748b; color: var(--text-color-secondary);
} }
.card-row-1 .amount-detail.tva { .card-row-1 .amount-detail.tva {
color: #16a34a; color: var(--green-600);
} }
.card-row-1 .amount-detail.payment { .card-row-1 .amount-detail.payment {
color: #7c3aed; color: var(--purple-600);
} }
.card-row-2 { .card-row-2 {
@@ -1148,12 +1160,12 @@ const approveSelected = async () => {
align-items: center; align-items: center;
gap: 0.375rem; gap: 0.375rem;
font-size: 0.75rem; font-size: 0.75rem;
color: #64748b; color: var(--text-color-secondary);
flex-wrap: wrap; flex-wrap: wrap;
} }
.card-row-2 .sep { .card-row-2 .sep {
color: #cbd5e1; color: var(--surface-border);
} }
.card-row-2 .receipt-nr { .card-row-2 .receipt-nr {
@@ -1161,12 +1173,12 @@ const approveSelected = async () => {
} }
.card-row-2 .direction-out { .card-row-2 .direction-out {
color: #dc2626; color: var(--red-600);
font-weight: 500; font-weight: 500;
} }
.card-row-2 .direction-in { .card-row-2 .direction-in {
color: #16a34a; color: var(--green-600);
font-weight: 500; font-weight: 500;
} }
@@ -1175,12 +1187,12 @@ const approveSelected = async () => {
align-items: center; align-items: center;
gap: 0.375rem; gap: 0.375rem;
font-size: 0.7rem; font-size: 0.7rem;
color: #94a3b8; color: var(--text-color-secondary);
margin-top: 0.25rem; margin-top: 0.25rem;
} }
.card-row-3 .sep { .card-row-3 .sep {
color: #cbd5e1; color: var(--surface-border);
} }
.card-row-3 .created-by { .card-row-3 .created-by {
@@ -1192,7 +1204,7 @@ const approveSelected = async () => {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.125rem; gap: 0.125rem;
color: #64748b; color: var(--text-color-secondary);
} }
/* Small status badge for mobile cards */ /* Small status badge for mobile cards */
@@ -1204,18 +1216,18 @@ const approveSelected = async () => {
text-transform: uppercase; text-transform: uppercase;
} }
.status-badge-small.status-draft { background-color: #e3f2fd; color: #1976d2; } .status-badge-small.status-draft { background-color: var(--blue-100); color: var(--blue-600); }
.status-badge-small.status-pending { background-color: #fff3e0; color: #f57c00; } .status-badge-small.status-pending { background-color: var(--orange-100); color: var(--orange-600); }
.status-badge-small.status-approved { background-color: #e8f5e9; color: #388e3c; } .status-badge-small.status-approved { background-color: var(--green-100); color: var(--green-600); }
.status-badge-small.status-rejected { background-color: #ffebee; color: #d32f2f; } .status-badge-small.status-rejected { background-color: var(--red-100); color: var(--red-600); }
.status-badge-small.status-synced { background-color: #e0f2f1; color: #00796b; } .status-badge-small.status-synced { background-color: var(--teal-100); color: var(--teal-600); }
/* Status text colors for mobile */ /* Status text colors for mobile */
.status-text-draft { color: #1976d2; } .status-text-draft { color: var(--blue-600); }
.status-text-pending_review { color: #f57c00; } .status-text-pending_review { color: var(--orange-600); }
.status-text-approved { color: #388e3c; } .status-text-approved { color: var(--green-600); }
.status-text-rejected { color: #d32f2f; } .status-text-rejected { color: var(--red-600); }
.status-text-synced { color: #00796b; } .status-text-synced { color: var(--teal-600); }
/* Mobile Pagination */ /* Mobile Pagination */
.mobile-pagination { .mobile-pagination {
@@ -1230,7 +1242,7 @@ const approveSelected = async () => {
.page-info { .page-info {
font-size: 0.875rem; font-size: 0.875rem;
font-weight: 500; font-weight: 500;
color: #475569; color: var(--text-color-secondary);
min-width: 60px; min-width: 60px;
text-align: center; text-align: center;
} }
@@ -1349,18 +1361,15 @@ const approveSelected = async () => {
.reject-dialog-content p { .reject-dialog-content p {
margin: 0; margin: 0;
color: #475569; color: var(--text-color-secondary);
} }
/* Text muted helper */ /* Text muted helper */
.text-muted { .text-muted {
color: #94a3b8; color: var(--text-color-secondary);
} }
/* Compact table adjustments for more columns */ /* Compact table adjustments - styles moved to vendor/primevue-overrides.css */
.compact-table :deep(.p-datatable-wrapper) {
overflow-x: auto;
}
/* Bulk Actions Bar */ /* Bulk Actions Bar */
.bulk-actions-bar { .bulk-actions-bar {
@@ -1368,8 +1377,8 @@ const approveSelected = async () => {
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%); background: linear-gradient(135deg, var(--blue-50) 0%, var(--blue-100) 100%);
border: 1px solid #bfdbfe; border: 1px solid var(--blue-200);
border-radius: 6px; border-radius: 6px;
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
} }
@@ -1379,7 +1388,7 @@ const approveSelected = async () => {
align-items: center; align-items: center;
gap: 0.5rem; gap: 0.5rem;
font-weight: 600; font-weight: 600;
color: #1e40af; color: var(--blue-700);
} }
.selection-info i { .selection-info i {

View File

@@ -580,7 +580,7 @@ watch(
} }
.detailed-table th { .detailed-table th {
background: #ffffff; background: var(--surface-card);
font-weight: 500; font-weight: 500;
color: var(--color-text-primary); color: var(--color-text-primary);
position: sticky; position: sticky;
@@ -592,7 +592,7 @@ watch(
/* Group row styling */ /* Group row styling */
.group-row { .group-row {
background: #ffffff; background: var(--surface-card);
cursor: pointer; cursor: pointer;
font-weight: 500; font-weight: 500;
border-top: 1px solid var(--color-border); border-top: 1px solid var(--color-border);
@@ -600,26 +600,26 @@ watch(
} }
.group-row:hover { .group-row:hover {
background: #f8f9fa; background: var(--surface-hover);
} }
.group-row.has-restant:hover { .group-row.has-restant:hover {
background: #f8f9fa; background: var(--surface-hover);
} }
/* Single invoice row styling */ /* Single invoice row styling */
.single-invoice-row { .single-invoice-row {
background: #ffffff; background: var(--surface-card);
font-weight: 400; font-weight: 400;
transition: background 0.15s ease; transition: background 0.15s ease;
} }
.single-invoice-row:hover { .single-invoice-row:hover {
background: #f8f9fa; background: var(--surface-hover);
} }
.single-invoice-row.row-restant:hover { .single-invoice-row.row-restant:hover {
background: #f8f9fa; background: var(--surface-hover);
} }
.single-invoice-row td:first-child { .single-invoice-row td:first-child {

View File

@@ -878,23 +878,7 @@ watch(
display: block; display: block;
} }
/* Enhanced striped rows with better contrast - same as Trial Balance */ /* Enhanced striped rows - moved to vendor/primevue-overrides.css with design tokens */
.table-card :deep(.p-datatable .p-datatable-tbody > tr) {
transition: background-color 0.2s ease;
}
.table-card :deep(.p-datatable .p-datatable-tbody > tr:nth-child(odd)) {
background-color: #ffffff;
}
.table-card :deep(.p-datatable .p-datatable-tbody > tr:nth-child(even)) {
background-color: #f8f9fa;
}
.table-card :deep(.p-datatable .p-datatable-tbody > tr:hover) {
background-color: #e3f2fd !important;
cursor: pointer;
}
/* Responsive design */ /* Responsive design */
@media (max-width: 768px) { @media (max-width: 768px) {

View File

@@ -343,8 +343,8 @@ onUnmounted(() => {
.logs-container { .logs-container {
max-height: 600px; max-height: 600px;
overflow-y: auto; overflow-y: auto;
background: #ffffff; background: var(--surface-card);
border: 1px solid #e2e8f0; border: 1px solid var(--surface-border);
border-radius: var(--radius-md); border-radius: var(--radius-md);
padding: var(--space-sm); padding: var(--space-sm);
} }

View File

@@ -282,9 +282,9 @@ onUnmounted(() => {
display: flex; display: flex;
justify-content: center; justify-content: center;
padding: var(--space-lg); padding: var(--space-lg);
background: white; background: var(--surface-card);
border-radius: var(--radius-md); border-radius: var(--radius-md);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); box-shadow: var(--shadow-sm);
} }
/* Responsive - Telegram-specific adjustments */ /* Responsive - Telegram-specific adjustments */

View File

@@ -20,7 +20,7 @@
</router-link> </router-link>
</div> </div>
<!-- Right side: Period + Company + User --> <!-- Right side: Period + Company + Theme + User -->
<div class="header-actions"> <div class="header-actions">
<PeriodSelector <PeriodSelector
v-if="showPeriod && selectedCompany" v-if="showPeriod && selectedCompany"
@@ -36,6 +36,15 @@
variant="header" variant="header"
@company-changed="onCompanyChanged" @company-changed="onCompanyChanged"
/> />
<!-- Theme Toggle Button -->
<button
class="theme-toggle-btn"
@click="cycleTheme"
:title="themeLabel"
aria-label="Change theme"
>
<i :class="themeIcon"></i>
</button>
<!-- User menu slot (only rendered if showUser is true) --> <!-- User menu slot (only rendered if showUser is true) -->
<div v-if="showUser"> <div v-if="showUser">
<slot name="user-menu"> <slot name="user-menu">
@@ -51,7 +60,7 @@
</template> </template>
<script> <script>
import { computed } from "vue"; import { computed, ref, onMounted } from "vue";
import CompanySelector from "../CompanySelector.vue"; import CompanySelector from "../CompanySelector.vue";
import PeriodSelector from "../PeriodSelector.vue"; import PeriodSelector from "../PeriodSelector.vue";
@@ -125,10 +134,54 @@ export default {
emit("period-changed", period); emit("period-changed", period);
}; };
// Theme toggle logic
const currentTheme = ref(localStorage.getItem('user-theme') || 'auto');
const themeIcon = computed(() => ({
'auto': 'pi pi-desktop',
'light': 'pi pi-sun',
'dark': 'pi pi-moon'
}[currentTheme.value]));
const themeLabel = computed(() => ({
'auto': 'Tema: Auto (sistem)',
'light': 'Tema: Light',
'dark': 'Tema: Dark'
}[currentTheme.value]));
const applyTheme = (theme) => {
if (theme === 'auto') {
document.documentElement.removeAttribute('data-theme');
localStorage.removeItem('user-theme');
} else {
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem('user-theme', theme);
}
};
const cycleTheme = () => {
const themes = ['auto', 'light', 'dark'];
const idx = themes.indexOf(currentTheme.value);
const next = themes[(idx + 1) % 3];
currentTheme.value = next;
applyTheme(next);
};
// Initialize theme on mount
onMounted(() => {
if (currentTheme.value !== 'auto') {
applyTheme(currentTheme.value);
}
});
return { return {
selectedCompany, selectedCompany,
onCompanyChanged, onCompanyChanged,
onPeriodChanged, onPeriodChanged,
currentTheme,
themeIcon,
themeLabel,
cycleTheme,
}; };
}, },
}; };

View File

@@ -132,6 +132,42 @@
background-color: rgba(255, 255, 255, 0.1); background-color: rgba(255, 255, 255, 0.1);
} }
/* Theme Toggle Button */
.theme-toggle-btn {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
background: transparent;
border: 1px solid var(--surface-border, #e2e8f0);
border-radius: var(--radius-md, 6px);
cursor: pointer;
color: var(--text-color, #111827);
transition: all 0.15s ease;
}
.theme-toggle-btn:hover {
background: var(--surface-hover, #f1f5f9);
border-color: var(--primary-500, #3b82f6);
color: var(--primary-500, #3b82f6);
}
.theme-toggle-btn i {
font-size: 1rem;
}
/* Gradient header theme toggle */
.header-container--gradient .theme-toggle-btn {
color: white;
border-color: rgba(255, 255, 255, 0.3);
}
.header-container--gradient .theme-toggle-btn:hover {
background: rgba(255, 255, 255, 0.1);
border-color: white;
}
/* Mobile Responsive */ /* Mobile Responsive */
@media (max-width: 768px) { @media (max-width: 768px) {
.header-container { .header-container {

View File

@@ -23,12 +23,13 @@
border-radius: 16px; border-radius: 16px;
overflow: hidden; overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.1);
background: var(--surface-card);
} }
.login-header { .login-header {
text-align: center; text-align: center;
padding: 2rem 2rem 1rem 2rem; padding: 2rem 2rem 1rem 2rem;
background: white; background: var(--surface-card);
} }
.login-title { .login-title {
@@ -46,6 +47,7 @@
.login-form { .login-form {
padding: 0 2rem 2rem 2rem; padding: 0 2rem 2rem 2rem;
background: var(--surface-card);
} }
.login-button { .login-button {

View File

@@ -10,6 +10,10 @@ set -e # Exit on any error
# Get the directory where this script is located # Get the directory where this script is located
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Load NVM if available (required for npm)
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
# Colors for output # Colors for output
RED='\033[0;31m' RED='\033[0;31m'
GREEN='\033[0;32m' GREEN='\033[0;32m'
@@ -220,7 +224,8 @@ else
# Start frontend # Start frontend
print_message "Starting Vite development server..." print_message "Starting Vite development server..."
nohup npm run dev > /tmp/unified_frontend_prod.log 2>&1 & # Use bash -c to ensure NVM environment is loaded for nohup
nohup bash -c 'export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"; npm run dev' > /tmp/unified_frontend_prod.log 2>&1 &
FRONTEND_PID=$! FRONTEND_PID=$!
# Wait for frontend to start (Vite can take 8-10 seconds) # Wait for frontend to start (Vite can take 8-10 seconds)