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>
431 lines
7.6 KiB
CSS
431 lines
7.6 KiB
CSS
/* Button Components - ROA2WEB */
|
|
|
|
/* Base Button Styles */
|
|
.btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: var(--space-xs);
|
|
padding: var(--space-sm) var(--space-md);
|
|
font-size: var(--text-sm);
|
|
font-weight: var(--font-medium);
|
|
line-height: var(--leading-normal);
|
|
border: 1px solid transparent;
|
|
border-radius: var(--radius-md);
|
|
cursor: pointer;
|
|
text-decoration: none;
|
|
transition: all var(--transition-fast);
|
|
user-select: none;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.btn:disabled {
|
|
opacity: 0.6;
|
|
cursor: not-allowed;
|
|
transform: none !important;
|
|
}
|
|
|
|
/* Button Sizes */
|
|
.btn-xs {
|
|
padding: var(--space-xs) var(--space-sm);
|
|
font-size: var(--text-xs);
|
|
gap: 2px;
|
|
}
|
|
|
|
.btn-sm {
|
|
padding: var(--space-xs) var(--space-sm);
|
|
font-size: var(--text-sm);
|
|
}
|
|
|
|
.btn-md {
|
|
padding: var(--space-sm) var(--space-md);
|
|
font-size: var(--text-sm);
|
|
}
|
|
|
|
.btn-lg {
|
|
padding: var(--space-md) var(--space-lg);
|
|
font-size: var(--text-base);
|
|
}
|
|
|
|
.btn-xl {
|
|
padding: var(--space-lg) var(--space-xl);
|
|
font-size: var(--text-lg);
|
|
}
|
|
|
|
/* Button Variants */
|
|
.btn-primary {
|
|
background: var(--color-primary);
|
|
color: var(--color-text-inverse);
|
|
border-color: var(--color-primary);
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
background: var(--color-primary-dark);
|
|
border-color: var(--color-primary-dark);
|
|
transform: translateY(-1px);
|
|
box-shadow: var(--shadow-md);
|
|
}
|
|
|
|
.btn-secondary {
|
|
background: var(--color-bg);
|
|
color: var(--color-text);
|
|
border-color: var(--color-border);
|
|
}
|
|
|
|
.btn-secondary:hover {
|
|
background: var(--color-bg-secondary);
|
|
border-color: var(--color-border-dark);
|
|
}
|
|
|
|
.btn-outline {
|
|
background: transparent;
|
|
color: var(--color-primary);
|
|
border-color: var(--color-primary);
|
|
}
|
|
|
|
.btn-outline:hover {
|
|
background: var(--color-primary);
|
|
color: var(--color-text-inverse);
|
|
}
|
|
|
|
.btn-ghost {
|
|
background: transparent;
|
|
color: var(--color-text);
|
|
border-color: transparent;
|
|
}
|
|
|
|
.btn-ghost:hover {
|
|
background: var(--color-bg-secondary);
|
|
border-color: var(--color-border);
|
|
}
|
|
|
|
/* Status Button Variants */
|
|
.btn-success {
|
|
background: var(--color-success);
|
|
color: var(--color-text-inverse);
|
|
border-color: var(--color-success);
|
|
}
|
|
|
|
.btn-success:hover {
|
|
background: #047857;
|
|
border-color: #047857;
|
|
}
|
|
|
|
.btn-warning {
|
|
background: var(--color-warning);
|
|
color: var(--color-text-inverse);
|
|
border-color: var(--color-warning);
|
|
}
|
|
|
|
.btn-warning:hover {
|
|
background: #b45309;
|
|
border-color: #b45309;
|
|
}
|
|
|
|
.btn-error {
|
|
background: var(--color-error);
|
|
color: var(--color-text-inverse);
|
|
border-color: var(--color-error);
|
|
}
|
|
|
|
.btn-error:hover {
|
|
background: #b91c1c;
|
|
border-color: #b91c1c;
|
|
}
|
|
|
|
/* Button Shapes */
|
|
.btn-rounded {
|
|
border-radius: var(--radius-full);
|
|
}
|
|
|
|
.btn-square {
|
|
border-radius: 0;
|
|
}
|
|
|
|
.btn-circle {
|
|
border-radius: var(--radius-full);
|
|
width: 40px;
|
|
height: 40px;
|
|
padding: 0;
|
|
}
|
|
|
|
.btn-circle.btn-sm {
|
|
width: 32px;
|
|
height: 32px;
|
|
}
|
|
|
|
.btn-circle.btn-lg {
|
|
width: 48px;
|
|
height: 48px;
|
|
}
|
|
|
|
/* Icon Buttons */
|
|
.btn-icon {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 40px;
|
|
height: 40px;
|
|
padding: 0;
|
|
border-radius: var(--radius-md);
|
|
}
|
|
|
|
.btn-icon-sm {
|
|
width: 32px;
|
|
height: 32px;
|
|
}
|
|
|
|
.btn-icon-lg {
|
|
width: 48px;
|
|
height: 48px;
|
|
}
|
|
|
|
/* Button Groups */
|
|
.btn-group {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.btn-group .btn {
|
|
border-radius: 0;
|
|
border-right-width: 0;
|
|
}
|
|
|
|
.btn-group .btn:first-child {
|
|
border-top-left-radius: var(--radius-md);
|
|
border-bottom-left-radius: var(--radius-md);
|
|
}
|
|
|
|
.btn-group .btn:last-child {
|
|
border-top-right-radius: var(--radius-md);
|
|
border-bottom-right-radius: var(--radius-md);
|
|
border-right-width: 1px;
|
|
}
|
|
|
|
.btn-group .btn:hover {
|
|
z-index: 1;
|
|
border-right-width: 1px;
|
|
}
|
|
|
|
/* Action Buttons for Dashboard V4 */
|
|
.action-btn {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: var(--space-md);
|
|
padding: var(--space-xl);
|
|
background: var(--color-bg);
|
|
border: 1px solid var(--color-border);
|
|
border-radius: var(--card-radius);
|
|
cursor: pointer;
|
|
transition: all var(--transition-fast);
|
|
text-decoration: none;
|
|
color: var(--color-text);
|
|
min-height: 120px;
|
|
}
|
|
|
|
.action-btn:hover {
|
|
background: var(--color-primary);
|
|
color: var(--color-text-inverse);
|
|
border-color: var(--color-primary);
|
|
transform: translateY(-2px);
|
|
box-shadow: var(--shadow-lg);
|
|
}
|
|
|
|
.action-btn-icon {
|
|
width: 32px;
|
|
height: 32px;
|
|
opacity: 0.8;
|
|
}
|
|
|
|
.action-btn:hover .action-btn-icon {
|
|
opacity: 1;
|
|
}
|
|
|
|
.action-btn-label {
|
|
font-size: var(--text-sm);
|
|
font-weight: var(--font-semibold);
|
|
text-align: center;
|
|
}
|
|
|
|
/* Toggle Buttons */
|
|
.btn-toggle {
|
|
background: var(--color-bg-secondary);
|
|
color: var(--color-text-secondary);
|
|
border-color: var(--color-border);
|
|
}
|
|
|
|
.btn-toggle.active,
|
|
.btn-toggle:hover {
|
|
background: var(--color-primary);
|
|
color: var(--color-text-inverse);
|
|
border-color: var(--color-primary);
|
|
}
|
|
|
|
/* Pill Buttons */
|
|
.btn-pill {
|
|
border-radius: var(--radius-full);
|
|
padding: var(--space-xs) var(--space-md);
|
|
font-size: var(--text-xs);
|
|
font-weight: var(--font-medium);
|
|
}
|
|
|
|
/* Loading State */
|
|
.btn-loading {
|
|
opacity: 0.7;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.btn-loading::before {
|
|
content: "";
|
|
display: inline-block;
|
|
width: 16px;
|
|
height: 16px;
|
|
margin-right: var(--space-xs);
|
|
border: 2px solid currentColor;
|
|
border-right-color: transparent;
|
|
border-radius: var(--radius-full);
|
|
animation: btn-spin 0.8s linear infinite;
|
|
}
|
|
|
|
@keyframes btn-spin {
|
|
to {
|
|
transform: rotate(360deg);
|
|
}
|
|
}
|
|
|
|
/* Mobile Button Adjustments */
|
|
@media (max-width: 768px) {
|
|
.btn {
|
|
padding: var(--space-md) var(--space-lg);
|
|
font-size: var(--text-base);
|
|
min-height: 44px;
|
|
}
|
|
|
|
.btn-sm {
|
|
padding: var(--space-sm) var(--space-md);
|
|
font-size: var(--text-sm);
|
|
min-height: 36px;
|
|
}
|
|
|
|
.btn-group {
|
|
flex-direction: column;
|
|
width: 100%;
|
|
}
|
|
|
|
.btn-group .btn {
|
|
border-radius: 0;
|
|
border-right-width: 1px;
|
|
border-bottom-width: 0;
|
|
width: 100%;
|
|
}
|
|
|
|
.btn-group .btn:first-child {
|
|
border-radius: var(--radius-md) var(--radius-md) 0 0;
|
|
}
|
|
|
|
.btn-group .btn:last-child {
|
|
border-radius: 0 0 var(--radius-md) var(--radius-md);
|
|
border-bottom-width: 1px;
|
|
}
|
|
|
|
.action-btn {
|
|
padding: var(--space-lg);
|
|
min-height: 100px;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 480px) {
|
|
.action-btn {
|
|
min-height: 80px;
|
|
padding: var(--space-md);
|
|
}
|
|
|
|
.action-btn-icon {
|
|
width: 24px;
|
|
height: 24px;
|
|
}
|
|
|
|
.action-btn-label {
|
|
font-size: var(--text-xs);
|
|
}
|
|
}
|
|
|
|
/* Focus States for Accessibility */
|
|
.btn:focus-visible {
|
|
outline: 2px solid var(--color-primary);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
/* Button Groups for Dashboard */
|
|
.button-group {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.25rem;
|
|
}
|
|
|
|
.button-group .btn {
|
|
border-radius: var(--radius-md);
|
|
}
|
|
|
|
/* Hide button text on small screens */
|
|
@media (max-width: 640px) {
|
|
.btn-text {
|
|
display: none;
|
|
}
|
|
|
|
.button-group {
|
|
width: 100%;
|
|
}
|
|
|
|
.button-group .btn {
|
|
flex: 1;
|
|
justify-content: center;
|
|
}
|
|
}
|
|
|
|
/* Stack buttons vertically on very small screens */
|
|
@media (max-width: 480px) {
|
|
.button-group {
|
|
flex-direction: column;
|
|
width: 100%;
|
|
}
|
|
|
|
.button-group .btn {
|
|
width: 100%;
|
|
}
|
|
|
|
.btn-text {
|
|
display: inline; /* Show text again when stacked */
|
|
}
|
|
}
|
|
|
|
/* Primary button style for exports */
|
|
.btn-primary {
|
|
background: var(--color-primary);
|
|
color: white;
|
|
border-color: var(--color-primary);
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
background: var(--color-primary-dark);
|
|
border-color: var(--color-primary-dark);
|
|
transform: translateY(-1px);
|
|
box-shadow: var(--shadow-md);
|
|
}
|
|
|
|
.btn-primary:active {
|
|
transform: translateY(0);
|
|
}
|
|
|
|
/* Utility Classes */
|
|
.btn-full-width {
|
|
width: 100%;
|
|
justify-content: center;
|
|
}
|
|
|
|
.btn-auto-width {
|
|
width: auto;
|
|
}
|