feat: Implement unified Vue SPA with granular service control

Consolidate Reports and Data Entry apps into a single Vue.js SPA with:

Architecture:
- Module-based structure with lazy-loaded routes (@reports, @data-entry)
- Error boundaries per module to prevent cascade failures
- Dual API proxy in Vite for microservices (reports:8001, data-entry:8003)
- Pinia store factories for shared auth, company, and period stores
- Vite path aliases for clear module boundaries (@shared, @reports, @data-entry)

Service Management:
- Granular service control scripts (backend-reports.sh, backend-data-entry.sh, bot.sh, frontend.sh)
- 87% faster frontend restart: 7s vs 53s full restart
- 38% faster full startup: 33s vs 53s via parallel backend initialization
- Enhanced start-dev.sh with proper service timeouts (OCR: 30s, Vite: 15s, Bot: 10s)
- status.sh for comprehensive health checks

Features:
- Auto-select first company on login with period auto-load
- Hamburger menu with feature toggle support
- JWT token auto-injection via axios interceptors
- Unified header with company/period selectors
- IIS web.config for production deployment with multi-API routing

UX Improvements:
- Vue watchers for reactive company/period loading
- Lazy store initialization with graceful error handling
- Period persistence per user+company in localStorage
- Feature flags for optional modules

Deployment:
- Single IIS site serves unified frontend with API proxy rules
- Maintains separate backend processes for microservices
- Windows line ending fixes (.env CRLF → LF conversion)

Stats: 112 files changed, 38,342 insertions(+), 2,342 deletions(-)

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-24 19:06:23 +02:00
parent fed2e68fa2
commit d507a81b0a
112 changed files with 38382 additions and 2382 deletions

View File

@@ -0,0 +1,138 @@
/* PrimeVue Component Overrides - ROA2WEB */
/* Global customization of PrimeVue saga-blue theme */
/* ===== Input Components ===== */
.p-inputtext,
.p-password input,
.p-dropdown,
.p-calendar input,
.p-autocomplete input {
border: 2px solid var(--color-border) !important;
border-radius: var(--radius-md) !important;
padding: var(--space-sm) var(--space-md) !important;
font-size: var(--text-base) !important;
font-family: inherit !important;
color: var(--color-text) !important;
background: var(--color-bg) !important;
transition: all var(--transition-fast) !important;
min-height: 44px !important;
}
/* ===== Focus States ===== */
.p-inputtext:focus,
.p-password input:focus,
.p-dropdown:focus,
.p-calendar input:focus,
.p-autocomplete input:focus {
outline: none !important;
border-color: var(--color-primary) !important;
box-shadow: var(--focus-ring) !important;
}
/* ===== Hover States ===== */
.p-inputtext:hover:not(:disabled),
.p-password input:hover:not(:disabled),
.p-dropdown:hover:not(:disabled) {
border-color: var(--color-border-dark, #d1d5db) !important;
}
/* ===== Disabled States ===== */
.p-inputtext:disabled,
.p-password input:disabled,
.p-dropdown:disabled {
background: var(--color-bg-muted, #f3f4f6) !important;
color: var(--color-text-muted, #9ca3af) !important;
opacity: 0.6 !important;
cursor: not-allowed !important;
}
/* ===== Validation States ===== */
.p-invalid.p-component,
.p-inputtext.p-invalid,
.p-password.p-invalid input {
border-color: var(--color-error, #ef4444) !important;
}
/* ===== Button Overrides ===== */
.p-button {
padding: var(--space-sm) var(--space-md) !important;
font-size: var(--text-sm) !important;
font-weight: var(--font-medium) !important;
border-radius: var(--radius-md) !important;
transition: all var(--transition-fast) !important;
}
.p-button:hover {
transform: translateY(-1px) !important;
box-shadow: var(--shadow-md) !important;
}
/* ===== DataTable ===== */
.p-datatable .p-datatable-thead > tr > th {
background: var(--color-bg-muted, #f9fafb) !important;
color: var(--color-text) !important;
font-weight: var(--font-semibold) !important;
border-bottom: 2px solid var(--color-border) !important;
padding: var(--space-md) var(--space-lg) !important;
}
.p-datatable .p-datatable-tbody > tr {
transition: background-color var(--transition-fast) !important;
}
/* DataTable Striped Rows - Global Pattern */
.p-datatable .p-datatable-tbody > tr:nth-child(odd) {
background-color: #ffffff !important;
}
.p-datatable .p-datatable-tbody > tr:nth-child(even) {
background-color: #f8f9fa !important;
}
.p-datatable .p-datatable-tbody > tr:hover {
background-color: #e3f2fd !important;
cursor: pointer;
}
/* Compact DataTable variant (p-datatable-sm) */
.p-datatable-sm .p-datatable-thead > tr > th {
padding: 0.5rem 0.75rem !important;
font-weight: 600 !important;
white-space: nowrap !important;
}
.p-datatable-sm .p-datatable-tbody > tr > td {
padding: 0.4rem 0.75rem !important;
}
/* DataTable font size for compact tables */
.p-datatable-sm {
font-size: 0.875rem !important;
}
/* ===== Card ===== */
.p-card {
box-shadow: var(--shadow-sm) !important;
border: 1px solid var(--color-border) !important;
border-radius: var(--card-radius, 8px) !important;
}
.p-card .p-card-header {
background: var(--color-bg-secondary) !important;
border-bottom: 1px solid var(--color-border) !important;
padding: var(--space-lg) !important;
}
.p-card .p-card-body {
padding: var(--space-lg) !important;
}
/* ===== Mobile Optimizations ===== */
@media (max-width: 768px) {
.p-inputtext,
.p-password input,
.p-dropdown,
.p-calendar input {
font-size: 16px !important; /* Prevent iOS zoom */
}
}