refactored the admin.css

This commit is contained in:
admin 2025-08-16 00:04:05 -06:00
parent a96318f19e
commit 26717f89f7
20 changed files with 6152 additions and 3020 deletions

View File

@ -11,7 +11,7 @@ const keyGenerator = (req) => {
// General API rate limiter
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100,
max: 300, // Increased from 100 to 300 to accommodate auto-refresh and multiple users
keyGenerator,
standardHeaders: true,
legacyHeaders: false,

View File

@ -0,0 +1,63 @@
# CSS Refactoring Summary
## 🎯 Objective Completed
Successfully refactored the large `admin.css` file (3,011 lines) into 11 smaller, focused modules for better maintainability.
## 📁 New Structure Created
```
/css/admin/
├── variables.css (66 lines) - Theme configuration & CSS custom properties
├── layout.css (244 lines) - Admin container, sidebar, navigation components
├── forms.css (251 lines) - Form fields, buttons, and input styling
├── status-messages.css (189 lines) - Toast notifications & status indicators
├── user-management.css (219 lines) - User tables, roles, and management UI
├── walk-sheet.css (302 lines) - Walk sheet preview, QR codes, print styles
├── data-convert.css (215 lines) - CSV upload and data processing interface
├── nocodb-links.css (135 lines) - External database integration cards
├── cuts-shifts.css (197 lines) - Cuts and shifts management interface
├── modals.css (310 lines) - Modal dialogs and email composition
└── responsive.css (717 lines) - Mobile, tablet, and desktop breakpoints
```
## ✅ Benefits Achieved
1. **Better Organization**: Styles logically grouped by functionality
2. **Improved Maintainability**: Each module is focused and self-contained
3. **Faster Development**: Easier to find and modify specific components
4. **Consistent Design**: Centralized variables ensure design consistency
5. **Easier Debugging**: Issues can be quickly traced to relevant modules
6. **Future-Proof**: Modular structure supports easier updates and expansion
## 🔧 Technical Details
- **Original File**: 3,011 lines in single `admin.css`
- **Refactored**: 11 modules totaling 2,845 lines + 16-line main import file
- **Backup**: Original file preserved as `admin.css.backup`
- **Compatibility**: No changes needed to existing HTML - refactoring is transparent
- **Import Structure**: Main file uses CSS `@import` for clean module loading
## 📋 File Breakdown
| Module | Lines | Purpose |
|--------|--------|---------|
| `variables.css` | 66 | CSS custom properties, theme colors, spacing |
| `layout.css` | 244 | Admin container, sidebar, navigation structure |
| `forms.css` | 251 | Form fields, buttons, validation states |
| `status-messages.css` | 189 | Notifications, progress bars, status icons |
| `user-management.css` | 219 | User tables, roles, volunteer management |
| `walk-sheet.css` | 302 | QR codes, printable layouts, form sections |
| `data-convert.css` | 215 | CSV upload, processing, results preview |
| `nocodb-links.css` | 135 | Database integration cards and status |
| `cuts-shifts.css` | 197 | Geographic cuts, shift scheduling |
| `modals.css` | 310 | Dialog boxes, email composition |
| `responsive.css` | 717 | Mobile and tablet responsive design |
## 🚀 Next Steps
The refactoring is complete and ready for use. Consider these future enhancements:
- **Performance**: Implement critical CSS loading
- **Documentation**: Add component usage examples
- **Testing**: Visual regression testing for UI components
- **Design System**: Expand variables into full design tokens

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,119 @@
# Admin CSS Refactoring
This directory contains the refactored admin panel CSS, broken down into smaller, more manageable modules for better maintainability.
## File Structure
```
/css/
├── admin.css # Main entry point with imports
├── admin.css.backup # Backup of original file
└── admin/ # Modular CSS files
├── variables.css # CSS variables and theme configuration
├── layout.css # Layout components (sidebar, content, navigation)
├── forms.css # Form styles and button variations
├── status-messages.css # Toast notifications and status indicators
├── user-management.css # User tables, forms, and management UI
├── walk-sheet.css # Walk sheet preview and QR code components
├── data-convert.css # CSV upload and data processing interface
├── nocodb-links.css # External database integration cards
├── cuts-shifts.css # Cuts and shifts management interface
├── modals.css # Modal dialogs and email composition
└── responsive.css # Mobile and tablet responsive styles
```
## Module Breakdown
### variables.css
- CSS custom properties for colors, spacing, typography
- Theme configuration and z-index layering
- Consistent design tokens across the application
### layout.css
- Admin container and sidebar layout
- Navigation components and menu items
- Desktop layout structure and crosshair utilities
### forms.css
- Form field styles and validation states
- Button variations (primary, secondary, danger, etc.)
- Input groups and form actions
### status-messages.css
- Toast notification system
- Progress bars and loading states
- Status icons and success/error indicators
### user-management.css
- User tables with sorting and filtering
- User role badges and expiration indicators
- Volunteer management components
### walk-sheet.css
- QR code configuration and display
- Printable walk sheet layout (8.5x11 paper)
- Form field circles and notes sections
### data-convert.css
- CSV file upload interface
- Data processing progress indicators
- Results preview with map integration
### nocodb-links.css
- External database integration cards
- Connection status indicators
- Information boxes and documentation
### cuts-shifts.css
- Geographic cuts management
- Shift scheduling interface
- Map interaction components
### modals.css
- Modal dialog base structure
- Email composition with rich text editing
- Progress tracking for email campaigns
### responsive.css
- Mobile-first responsive design
- Tablet and desktop breakpoints
- Touch-friendly interface adaptations
## Benefits of Refactoring
1. **Improved Maintainability**: Each module focuses on a specific area of functionality
2. **Better Organization**: Styles are logically grouped and easy to find
3. **Faster Development**: Developers can work on specific modules without conflicts
4. **Easier Debugging**: Issues can be quickly traced to their relevant module
5. **Better Performance**: Unused CSS can be more easily identified and removed
6. **Consistent Design**: Centralized variables ensure design consistency
## Usage
The main `admin.css` file imports all modules automatically. No changes are needed to existing HTML files - the refactoring is transparent to the application.
## Customization
To customize the admin interface:
1. **Colors and Theme**: Modify `variables.css`
2. **Layout Changes**: Edit `layout.css`
3. **Component Styles**: Update the relevant module (e.g., `forms.css` for button styles)
4. **Mobile Experience**: Adjust `responsive.css`
## File Size Comparison
- **Original**: ~3,011 lines in single file
- **Refactored**: ~11 focused modules averaging ~200-400 lines each
- **Total Size**: Approximately the same (no functionality removed)
- **Maintainability**: Significantly improved
## Future Improvements
Consider these enhancements for continued improvement:
1. **CSS Variables Expansion**: More granular theming options
2. **Component Documentation**: Add component examples and usage guidelines
3. **Performance Optimization**: Implement critical CSS loading
4. **Design System**: Expand into a full design system with documentation
5. **Automated Testing**: Add visual regression testing for UI components

View File

@ -0,0 +1,197 @@
/* Cuts and Shifts Management Interface */
/* Map interaction, form states, and administrative controls */
/* Cuts Section Styles */
.cuts-container {
display: flex;
flex-direction: column;
gap: var(--padding-base);
}
.cuts-map-section {
position: relative;
height: 500px;
background: #f5f5f5;
border-radius: 8px;
overflow: hidden;
border: 1px solid #ddd;
}
#cuts-map {
width: 100%;
height: 100%;
position: relative;
}
.cuts-form-section,
.cuts-list-section {
margin-top: var(--padding-base);
}
.cuts-filters {
display: grid;
grid-template-columns: 1fr 200px;
gap: 10px;
margin-bottom: 15px;
}
.cuts-filters input,
.cuts-filters select {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: var(--border-radius);
}
/* Disabled Form State */
.cut-form.disabled {
opacity: 0.7;
pointer-events: none;
}
.cut-form.disabled input:not(#start-drawing-btn):not(#reset-form-btn):not(#cancel-edit-btn),
.cut-form.disabled textarea,
.cut-form.disabled select {
background-color: #f5f5f5;
cursor: not-allowed;
}
.cut-form.disabled input:not(#start-drawing-btn):not(#reset-form-btn):not(#cancel-edit-btn):focus,
.cut-form.disabled textarea:focus,
.cut-form.disabled select:focus {
outline: none;
border-color: #ddd;
box-shadow: none;
}
/* Shifts Admin Styles */
.shifts-admin-container {
display: grid;
grid-template-columns: 400px 1fr;
gap: 30px;
}
.shift-form {
background: white;
padding: var(--padding-base);
border-radius: var(--border-radius);
border: 1px solid #e0e0e0;
}
.shifts-list {
background: white;
padding: var(--padding-base);
border-radius: var(--border-radius);
border: 1px solid #e0e0e0;
}
.shift-admin-item {
border: 1px solid #e0e0e0;
border-radius: var(--border-radius);
padding: 15px;
margin-bottom: 15px;
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
}
.shift-admin-item h4 {
margin: 0 0 10px 0;
}
.shift-admin-item p {
margin: 5px 0;
color: var(--secondary-color);
}
/* Shift Status Colors */
.status-open {
color: var(--success-color);
font-weight: bold;
}
.status-full {
color: var(--warning-color);
font-weight: bold;
}
.status-cancelled {
color: var(--error-color);
font-weight: bold;
}
.shift-actions {
display: flex;
gap: 10px;
}
.manage-volunteers-btn {
margin-left: 8px;
}
/* Shift Information Display */
.shift-info {
background-color: #f8f9fa;
padding: 15px;
border-radius: var(--border-radius);
margin-bottom: var(--padding-base);
}
.shift-info h4 {
margin: 0 0 8px 0;
color: var(--dark-color);
}
.shift-info p {
margin: 0;
color: #666;
font-size: var(--font-size-base);
}
/* Add User Section */
.add-user-section {
margin-bottom: 30px;
padding-bottom: var(--padding-base);
border-bottom: 1px solid #e0e0e0;
}
.add-user-section h4 {
margin: 0 0 15px 0;
color: var(--dark-color);
}
.current-volunteers-section h4 {
margin: 0 0 15px 0;
color: var(--dark-color);
}
.volunteer-actions-header {
display: flex;
justify-content: flex-end;
align-items: center;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
/* Large Screen Layout for Cuts */
@media (min-width: 1200px) {
.cuts-container {
display: grid;
grid-template-columns: 1fr;
gap: var(--padding-base);
}
.cuts-form-section,
.cuts-list-section {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--padding-base);
margin-top: 0;
}
.cuts-form-section > *,
.cuts-list-section > * {
grid-column: span 1;
}
}

View File

@ -0,0 +1,215 @@
/* Data Conversion Interface Styles */
/* CSV upload, processing, and results preview components */
.data-convert-container {
display: flex;
flex-direction: column;
gap: 2rem;
max-width: 1000px;
}
/* Upload Section */
.upload-section {
background: white;
border-radius: 8px;
padding: 2rem;
box-shadow: var(--shadow-sm);
}
.upload-area {
border: 2px dashed #ddd;
border-radius: 8px;
padding: 3rem;
text-align: center;
cursor: pointer;
transition: all 0.3s ease;
}
.upload-area:hover {
border-color: var(--primary-color);
background-color: #f8f9fa;
}
.upload-area.drag-over {
border-color: var(--primary-color);
background-color: #e3f2fd;
}
.upload-icon {
font-size: 48px;
margin-bottom: 1rem;
}
.file-info {
margin-top: 1rem;
padding: 1rem;
background: #f8f9fa;
border-radius: var(--border-radius);
}
/* CSV Requirements Section */
.csv-requirements {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: var(--padding-base);
margin: var(--padding-base) 0;
}
.csv-requirements h4 {
color: #495057;
margin-bottom: 15px;
border-bottom: 2px solid #e9ecef;
padding-bottom: 10px;
}
.requirements-section {
margin-bottom: var(--padding-base);
}
.requirements-section:last-child {
margin-bottom: 0;
}
.requirements-section h5 {
color: #6c757d;
margin-bottom: 10px;
font-weight: 600;
}
.field-mapping-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-top: 10px;
}
.field-group {
background: white;
border: 1px solid #e9ecef;
border-radius: var(--border-radius);
padding: 12px;
}
.field-group strong {
color: #495057;
display: block;
margin-bottom: 8px;
font-size: var(--font-size-base);
}
.field-group ul {
margin: 0;
padding-left: 15px;
list-style-type: disc;
}
.field-group li {
font-family: 'Courier New', monospace;
font-size: var(--font-size-xs);
color: #6c757d;
margin-bottom: 3px;
}
.csv-example {
background: #2d3748;
color: #e2e8f0;
padding: 15px;
border-radius: var(--border-radius);
font-family: 'Courier New', monospace;
font-size: var(--font-size-xs);
overflow-x: auto;
margin-top: 10px;
white-space: pre;
}
.requirements-section ul {
margin: 10px 0;
padding-left: var(--padding-base);
}
.requirements-section > ul > li {
margin-bottom: 5px;
color: #495057;
}
/* Processing Section */
.processing-section {
background: white;
border-radius: 8px;
padding: 2rem;
box-shadow: var(--shadow-sm);
}
.processing-actions {
display: flex;
gap: 1rem;
justify-content: center;
margin-top: 2rem;
}
/* Results Preview */
.results-preview {
margin-top: 2rem;
}
.results-map {
height: 400px;
margin-bottom: 1.5rem;
border: 1px solid #ddd;
border-radius: var(--border-radius);
}
.results-table-container {
max-height: 400px;
overflow-y: auto;
border: 1px solid #ddd;
border-radius: var(--border-radius);
}
.results-table {
width: 100%;
border-collapse: collapse;
}
.results-table th {
background: #f8f9fa;
padding: 0.75rem;
text-align: left;
font-weight: 600;
position: sticky;
top: 0;
z-index: 10;
}
.results-table td {
padding: 0.75rem;
border-bottom: 1px solid #e9ecef;
}
.result-success {
background: #d4edda;
animation: slideIn 0.3s ease-out;
}
.result-error {
background: #f8d7da;
animation: slideIn 0.3s ease-out;
}
.error-message {
color: #721c24;
font-style: italic;
}
/* Animation for results */
@keyframes slideIn {
from {
transform: translateX(-20px);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}

View File

@ -0,0 +1,251 @@
/* Form and Button Styles */
/* Reusable form components and button variations */
/* Form Components */
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: 500;
color: var(--dark-color);
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: var(--border-radius);
font-size: var(--font-size-base);
transition: border-color 0.2s;
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.2);
}
.form-row {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 10px;
}
.form-actions {
display: flex;
gap: 10px;
margin-top: 20px;
}
.help-text-inline {
font-size: var(--font-size-base);
color: #666;
margin-bottom: 15px;
}
/* Button Base Styles */
.btn {
padding: 8px 16px;
border: none;
border-radius: var(--border-radius);
font-size: var(--font-size-base);
font-weight: 500;
cursor: pointer;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 5px;
transition: var(--transition);
line-height: 1.4;
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
/* Button Size Variations */
.btn-sm {
padding: 6px 12px;
font-size: var(--font-size-sm);
}
.btn-lg {
padding: 12px 24px;
font-size: var(--font-size-lg);
}
/* Button Color Variations */
.btn-primary {
background-color: var(--primary-color);
color: white;
border: 1px solid var(--primary-color);
}
.btn-primary:hover:not(:disabled) {
background-color: #45a049;
transform: translateY(-1px);
}
.btn-secondary {
background-color: var(--secondary-color);
color: white;
border: 1px solid var(--secondary-color);
}
.btn-secondary:hover:not(:disabled) {
background-color: #5a6268;
transform: translateY(-1px);
}
.btn-success {
background-color: var(--success-color);
color: white;
border: 1px solid var(--success-color);
}
.btn-success:hover:not(:disabled) {
background-color: #218838;
border-color: #1e7e34;
}
.btn-danger {
background-color: var(--error-color);
color: white;
border: 1px solid var(--error-color);
}
.btn-danger:hover:not(:disabled) {
background-color: #c82333;
border-color: #bd2130;
transform: translateY(-1px);
}
.btn-warning {
background-color: var(--warning-color);
color: #212529;
border: 1px solid var(--warning-color);
}
.btn-warning:hover:not(:disabled) {
background-color: #e0a800;
border-color: #d39e00;
}
.btn-info {
background-color: var(--info-color);
color: white;
border: 1px solid var(--info-color);
}
.btn-info:hover:not(:disabled) {
background-color: #138496;
border-color: #117a8b;
}
/* Button States */
.btn-disabled,
.btn[disabled] {
background-color: var(--secondary-color);
border-color: var(--secondary-color);
color: white;
opacity: 0.6;
cursor: not-allowed;
pointer-events: none;
}
/* Enhanced Form Styles for Specific Contexts */
.user-form input[type="email"],
.user-form input[type="password"],
.user-form input[type="text"] {
width: 100%;
padding: 12px;
border: 2px solid #ddd;
border-radius: var(--border-radius);
font-size: var(--font-size-base);
transition: var(--transition);
background-color: #fafafa;
}
.user-form input:focus {
outline: none;
border-color: var(--primary-color);
background-color: white;
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1);
}
.user-form .form-group {
margin-bottom: 20px;
}
.user-form .form-group label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: var(--dark-color);
font-size: var(--font-size-base);
}
.user-form .form-group label input[type="checkbox"] {
margin-right: 8px;
transform: scale(1.1);
}
.user-form .form-actions {
display: flex;
gap: 10px;
margin-top: 25px;
}
.user-form .form-actions .btn {
flex: 1;
padding: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
font-size: var(--font-size-sm);
}
/* Special Form Field Types */
.form-field {
height: 30px;
background-color: #fafafa;
border: 1px solid #eee;
}
.form-field.circles {
height: auto;
background: none;
border: none;
display: flex;
gap: 15px;
align-items: center;
padding: 5px 0;
}
.circle-option {
display: flex;
align-items: center;
gap: 5px;
font-size: 11pt;
}
.circle {
width: 24px;
height: 24px;
border: 2px solid #333;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 10pt;
font-weight: 500;
background-color: white;
}

View File

@ -0,0 +1,244 @@
/* Admin Layout & Navigation Styles */
/* Core layout components: container, sidebar, content area, navigation */
.admin-container {
display: flex;
height: calc(100vh - var(--header-height));
height: calc(var(--app-height) - var(--header-height));
background-color: #f5f5f5;
width: 100%;
max-width: 100vw;
overflow: hidden;
}
.admin-sidebar {
width: var(--sidebar-width);
min-width: var(--sidebar-width);
max-width: var(--sidebar-width);
background-color: white;
border-right: 1px solid #e0e0e0;
padding: var(--padding-base);
box-sizing: border-box;
flex-shrink: 0;
}
.admin-sidebar h2 {
font-size: var(--font-size-xl);
margin-bottom: var(--padding-base);
color: var(--dark-color);
}
.admin-nav {
display: flex;
flex-direction: column;
gap: 5px;
}
.admin-nav a {
padding: 10px 15px;
color: var(--dark-color);
text-decoration: none;
border-radius: var(--border-radius);
transition: var(--transition);
}
.admin-nav a:hover {
background-color: var(--light-color);
}
.admin-nav a.active {
background-color: var(--primary-color);
color: white;
}
.admin-content {
flex: 1;
padding: 30px;
overflow-y: auto;
overflow-x: hidden;
width: 100%;
min-width: 0; /* Allow flex item to shrink */
box-sizing: border-box;
max-height: calc(100vh - var(--header-height));
max-height: calc(var(--app-height) - var(--header-height));
}
.admin-section {
background-color: white;
border-radius: var(--border-radius);
padding: 30px;
box-shadow: var(--shadow-sm);
width: 100%;
max-width: 100%;
box-sizing: border-box;
}
.admin-section h2 {
margin-bottom: 15px;
color: var(--dark-color);
}
.admin-section p {
color: #666;
margin-bottom: 25px;
}
.admin-map-container {
display: grid;
grid-template-columns: 1fr 300px;
gap: var(--padding-base);
margin-top: var(--padding-base);
}
.admin-map {
height: 500px;
border-radius: var(--border-radius);
border: 1px solid #ddd;
position: relative;
cursor: crosshair;
}
.admin-map .leaflet-container {
cursor: crosshair;
}
.location-controls {
padding: var(--padding-base);
background-color: #f9f9f9;
border-radius: var(--border-radius);
}
.location-controls .form-group {
margin-bottom: 15px;
}
.location-controls .form-actions {
display: flex;
flex-direction: column;
gap: 10px;
margin-top: var(--padding-base);
}
.help-text {
margin-top: var(--padding-base);
padding: 15px;
background-color: #e3f2fd;
border-radius: var(--border-radius);
font-size: var(--font-size-base);
}
.help-text p {
margin: 0;
color: #1976d2;
}
.admin-info {
display: flex;
align-items: center;
gap: 10px;
color: rgba(255,255,255,0.9);
font-size: var(--font-size-base);
}
/* Crosshair styling */
.crosshair {
pointer-events: none;
z-index: var(--z-dropdown);
}
.crosshair div {
border-radius: 1px;
opacity: 0.8;
}
/* Mobile Menu Components */
.mobile-menu-toggle {
display: none;
background: none;
border: none;
padding: 8px;
cursor: pointer;
position: relative;
width: 40px;
height: 40px;
}
.mobile-menu-toggle span {
display: block;
width: 24px;
height: 3px;
background: white;
margin: 5px auto;
transition: var(--transition);
border-radius: 2px;
}
.mobile-menu-toggle.active span:nth-child(1) {
transform: rotate(45deg) translate(5px, 5px);
}
.mobile-menu-toggle.active span:nth-child(2) {
opacity: 0;
}
.mobile-menu-toggle.active span:nth-child(3) {
transform: rotate(-45deg) translate(7px, -6px);
}
/* Sidebar Header & Footer (mobile) */
.sidebar-header {
display: none;
justify-content: space-between;
align-items: center;
padding-bottom: var(--padding-base);
border-bottom: 1px solid #e0e0e0;
margin-bottom: var(--padding-base);
}
.close-sidebar {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: var(--dark-color);
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
}
.sidebar-footer {
display: none;
margin-top: auto;
padding-top: var(--padding-base);
border-top: 1px solid #e0e0e0;
}
.mobile-admin-info {
margin-top: 15px;
font-size: var(--font-size-base);
color: #666;
text-align: center;
}
/* Navigation Icons */
.nav-icon {
margin-right: 10px;
font-size: var(--font-size-xl);
}
/* Utility Classes */
.desktop-only {
display: flex;
}
.mobile-only {
display: none;
}
.btn-block {
width: 100%;
justify-content: center;
}

View File

@ -0,0 +1,310 @@
/* Modal Dialogs and Email Interface */
/* Modal layouts, email composition, and interactive dialogs */
/* Modal Base Structure */
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: var(--z-modal);
}
.modal-content {
background-color: white;
border-radius: var(--border-radius);
padding: 0;
box-shadow: var(--shadow-lg);
max-width: 600px;
width: 90%;
max-height: 80vh;
overflow: hidden;
display: flex;
flex-direction: column;
}
.modal-large {
max-width: 800px;
width: 90%;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--padding-base) 25px;
border-bottom: 1px solid #e0e0e0;
background-color: #f8f9fa;
}
.modal-header h3 {
margin: 0;
color: var(--dark-color);
}
.modal-body {
padding: 25px;
overflow-y: auto;
flex: 1;
}
/* Email Form Components */
.email-form {
max-width: 100%;
}
.rich-text-toolbar {
display: flex;
gap: 5px;
margin-bottom: 10px;
padding: 10px;
background: #f8f9fa;
border: 1px solid #ddd;
border-radius: var(--border-radius) var(--border-radius) 0 0;
border-bottom: none;
}
.toolbar-btn {
padding: 8px 12px;
border: 1px solid #ccc;
background: white;
border-radius: 3px;
cursor: pointer;
font-size: var(--font-size-xs);
transition: var(--transition);
}
.toolbar-btn:hover {
background: #e9ecef;
border-color: #adb5bd;
}
.toolbar-btn:active,
.toolbar-btn.active {
background: #007bff;
color: white;
border-color: #007bff;
}
.rich-text-editor {
min-height: 200px;
border: 1px solid #ddd;
padding: 15px;
border-radius: 0 0 var(--border-radius) var(--border-radius);
background: white;
font-family: inherit;
font-size: var(--font-size-base);
line-height: 1.5;
outline: none;
}
.rich-text-editor:focus {
border-color: var(--primary-color);
box-shadow: 0 0 0 2px rgba(160, 44, 141, 0.2);
}
.rich-text-editor:empty:before {
content: attr(placeholder);
color: #999;
font-style: italic;
}
/* Email Preview */
.email-preview {
margin-top: var(--padding-base);
padding: 15px;
background: #f8f9fa;
border: 1px solid #ddd;
border-radius: var(--border-radius);
}
.email-preview-content {
background: white;
padding: var(--padding-base);
border: 1px solid #ccc;
border-radius: var(--border-radius);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.preview-header {
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid #eee;
}
.preview-body {
line-height: 1.6;
}
.email-recipients-info {
background: #e3f2fd;
padding: 15px;
border-radius: var(--border-radius);
margin: var(--padding-base) 0;
border-left: 4px solid #2196f3;
}
.email-recipients-info p {
margin: 0;
color: #1565c0;
}
/* Email Progress Components */
.email-progress-container {
display: none;
margin-top: var(--padding-base);
padding: var(--padding-base);
background-color: #f8f9fa;
border-radius: var(--border-radius);
border: 1px solid #dee2e6;
}
.email-progress-container.show {
display: block;
}
.progress-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.progress-title {
font-weight: 600;
color: var(--dark-color);
font-size: var(--font-size-lg);
}
.progress-stats {
display: flex;
gap: 15px;
font-size: var(--font-size-base);
}
.progress-stat {
display: flex;
align-items: center;
gap: 5px;
}
.progress-stat.success {
color: var(--success-color);
}
.progress-stat.error {
color: var(--error-color);
}
.progress-stat.pending {
color: #6c757d;
}
.progress-bar-container {
background-color: #e9ecef;
border-radius: 10px;
height: var(--padding-base);
overflow: hidden;
margin-bottom: 15px;
position: relative;
}
.progress-bar {
height: 100%;
background: linear-gradient(90deg, var(--primary-color) 0%, var(--success-color) 100%);
width: 0%;
transition: width 0.3s ease;
position: relative;
}
.progress-bar.complete {
background: linear-gradient(90deg, var(--success-color) 0%, #20c997 100%);
}
.progress-bar.error {
background: linear-gradient(90deg, var(--error-color) 0%, #e74c3c 100%);
}
.progress-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
font-size: var(--font-size-xs);
font-weight: 600;
text-shadow: 1px 1px 2px rgba(0,0,0,0.5);
}
.email-status-list {
max-height: 200px;
overflow-y: auto;
border: 1px solid #dee2e6;
border-radius: var(--border-radius);
background-color: white;
}
.email-status-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 15px;
border-bottom: 1px solid #f8f9fa;
font-size: var(--font-size-base);
}
.email-status-item:last-child {
border-bottom: none;
}
.email-status-recipient {
font-weight: 500;
color: var(--dark-color);
}
.email-status-result {
display: flex;
align-items: center;
gap: 5px;
font-size: var(--font-size-xs);
font-weight: 600;
}
.email-status-result.success {
color: var(--success-color);
}
.email-status-result.error {
color: var(--error-color);
}
.email-status-result.pending {
color: #6c757d;
}
.progress-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 15px;
}
.progress-close-btn {
background: var(--secondary-color);
color: white;
border: none;
padding: 8px 16px;
border-radius: var(--border-radius);
cursor: pointer;
font-size: var(--font-size-base);
transition: var(--transition);
}
.progress-close-btn:hover {
background: #5a6268;
}

View File

@ -0,0 +1,135 @@
/* NocoDB Links and External System Integration */
/* Card layouts and integration status for external databases */
.nocodb-links-container {
display: flex;
flex-direction: column;
gap: 30px;
}
.nocodb-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: var(--padding-base);
}
.nocodb-card {
background: white;
border-radius: 12px;
padding: 24px;
box-shadow: var(--shadow-md);
border: 1px solid #e0e0e0;
transition: var(--transition);
}
.nocodb-card:hover {
box-shadow: var(--shadow-lg);
transform: translateY(-2px);
}
.nocodb-card-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.nocodb-card-header h3 {
margin: 0;
font-size: var(--font-size-xl);
color: #333;
}
.nocodb-card-badge {
background: #e3f2fd;
color: #1976d2;
padding: 4px 12px;
border-radius: 20px;
font-size: var(--font-size-xs);
font-weight: 600;
text-transform: uppercase;
}
.nocodb-card p {
color: #666;
margin: 0 0 var(--padding-base) 0;
line-height: 1.5;
}
.nocodb-card .btn {
width: 100%;
justify-content: center;
}
.nocodb-card .btn.btn-disabled,
.nocodb-card .btn[disabled] {
background-color: var(--secondary-color);
border-color: var(--secondary-color);
color: white;
opacity: 0.6;
cursor: not-allowed;
pointer-events: none;
}
/* Info Box */
.nocodb-info {
margin-top: var(--padding-base);
}
.info-box {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 8px;
padding: var(--padding-base);
}
.info-box h4 {
margin: 0 0 12px 0;
color: #495057;
}
.info-box p {
margin: 0 0 12px 0;
color: #6c757d;
line-height: 1.5;
}
.info-box ul {
margin: 0;
padding-left: var(--padding-base);
color: #6c757d;
}
.info-box ul li {
margin: 6px 0;
}
.info-box ul li strong {
color: #495057;
}
/* Badge Color Variations */
.nocodb-card:nth-child(1) .nocodb-card-badge {
background: #e8f5e8;
color: #2e7d32;
}
.nocodb-card:nth-child(2) .nocodb-card-badge {
background: #fff3e0;
color: #f57c00;
}
.nocodb-card:nth-child(3) .nocodb-card-badge {
background: #f3e5f5;
color: #7b1fa2;
}
.nocodb-card:nth-child(4) .nocodb-card-badge {
background: #e1f5fe;
color: #0277bd;
}
.nocodb-card:nth-child(5) .nocodb-card-badge {
background: #e8f5e8;
color: #388e3c;
}

View File

@ -0,0 +1,717 @@
/* Responsive Design and Media Queries */
/* Mobile, tablet, and desktop layout adaptations */
/* Mobile Status Container */
@media (max-width: 768px) {
.status-container {
left: 10px;
right: 10px;
top: 10px;
width: auto;
max-width: 100%;
transform: none;
display: flex;
flex-direction: column;
align-items: center;
}
.status-message {
width: auto;
max-width: 90vw;
box-sizing: border-box;
font-size: var(--font-size-sm);
text-align: center;
}
}
/* Very Small Screens (under 480px) */
@media (max-width: 480px) {
.status-container {
top: 70px;
max-width: calc(100vw - 20px);
min-width: 260px;
}
}
@media (max-width: 360px) {
.status-container {
top: 65px;
max-width: calc(100vw - 15px);
min-width: auto;
}
.status-message {
padding: 10px 12px;
font-size: var(--font-size-sm);
}
}
/* Mobile Layout Adaptations */
@media (max-width: 768px) {
/* Show mobile menu toggle */
.mobile-menu-toggle {
display: block;
}
/* Header adjustments */
.header {
padding: 10px 15px;
display: flex;
align-items: center;
gap: 15px;
}
.header h1 {
flex: 1;
text-align: center;
margin: 0;
font-size: var(--font-size-xl);
}
/* Hide desktop elements */
.desktop-only {
display: none !important;
}
.mobile-only {
display: flex;
}
/* Admin container becomes full width */
.admin-container {
flex-direction: column;
height: calc(100vh - 50px);
height: calc(var(--app-height) - 50px);
}
/* Sidebar as overlay */
.admin-sidebar {
position: fixed;
top: 0;
left: -280px;
width: var(--sidebar-width-mobile);
height: 100vh;
height: var(--app-height);
background: white;
z-index: var(--z-modal-backdrop);
transition: left 0.3s ease;
box-shadow: var(--shadow-md);
display: flex;
flex-direction: column;
padding: var(--padding-base);
}
.admin-sidebar.active {
left: 0;
}
/* Show sidebar header and footer on mobile */
.sidebar-header {
display: flex;
}
.sidebar-footer {
display: block;
}
/* Admin nav mobile styling */
.admin-nav {
flex: 1;
display: flex;
flex-direction: column;
gap: 8px;
margin: var(--padding-base) 0;
}
.admin-nav a {
display: flex;
align-items: center;
padding: 10px 12px;
font-size: var(--font-size-base);
border-radius: var(--border-radius);
background-color: #f5f5f5;
border: 1px solid #e0e0e0;
transition: var(--transition);
}
.admin-nav a:active {
background-color: #e0e0e0;
transform: scale(0.98);
}
.admin-nav a.active {
background-color: var(--primary-color);
color: white;
border-color: var(--primary-color);
}
.admin-nav a:hover {
background-color: #ebebeb;
border-color: #d0d0d0;
}
.admin-nav a.active:hover {
background-color: #0056b3;
border-color: #0056b3;
}
/* Navigation icons and text */
.nav-icon {
font-size: var(--font-size-lg);
margin-right: 8px;
flex-shrink: 0;
}
.nav-text {
font-size: var(--font-size-base);
font-weight: 500;
}
/* Content adjustments */
.admin-content {
padding: 15px;
}
.admin-section {
padding: 15px;
}
/* Admin container mobile layout */
.admin-container {
flex-direction: column;
}
.admin-sidebar {
width: 100%;
border-right: none;
border-bottom: 1px solid #e0e0e0;
padding: 15px;
}
.admin-nav {
flex-direction: row;
overflow-x: auto;
gap: 10px;
padding-bottom: 10px;
}
.admin-nav a {
white-space: nowrap;
min-width: auto;
padding: 8px 12px;
font-size: var(--font-size-base);
}
.header .header-actions {
display: flex !important;
gap: 10px;
flex-wrap: wrap;
}
.header .header-actions .btn {
padding: 6px 10px;
font-size: var(--font-size-sm);
}
.admin-info {
font-size: var(--font-size-xs);
}
.admin-map-container {
grid-template-columns: 1fr;
gap: 15px;
}
.admin-map {
height: 250px;
}
.form-row {
grid-template-columns: 1fr;
}
/* User management mobile */
.users-admin-container {
grid-template-columns: 1fr;
gap: 25px;
}
.user-form,
.users-list {
padding: 25px;
width: 100%;
box-sizing: border-box;
}
.users-list {
overflow-x: auto;
width: 100%;
box-sizing: border-box;
}
/* Data Convert responsive styles */
.field-mapping-grid {
grid-template-columns: 1fr;
}
.csv-example {
font-size: 10px;
padding: 10px;
}
.csv-requirements {
padding: 15px;
}
/* NocoDB cards responsive */
.nocodb-cards {
grid-template-columns: 1fr;
gap: 16px;
}
.nocodb-card {
padding: var(--padding-base);
}
.nocodb-card-header h3 {
font-size: var(--font-size-lg);
}
.info-box {
padding: 16px;
}
/* Shifts admin mobile */
.shifts-admin-container {
grid-template-columns: 1fr;
gap: var(--padding-base);
}
.shift-admin-item {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
.shift-actions {
width: 100%;
justify-content: flex-end;
}
/* Walk sheet container mobile */
.walk-sheet-container {
display: flex !important;
flex-direction: column !important;
gap: var(--padding-base);
}
.walk-sheet-config {
order: 1 !important;
margin-bottom: var(--padding-base);
}
/* Walk sheet mobile */
.walk-sheet-preview {
min-width: 0;
max-width: 100%;
width: 100%;
min-height: 500px;
padding: 10px;
overflow: hidden;
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
}
.walk-sheet-preview #walk-sheet-preview-content {
transform: scale(0.35);
transform-origin: top center;
margin: 0 auto;
width: 816px;
max-width: none;
margin-bottom: -600px;
position: relative;
left: 50%;
margin-left: -408px;
}
/* Users table mobile card layout */
.users-table {
font-size: var(--font-size-base);
min-width: auto;
width: 100%;
table-layout: auto;
border-collapse: collapse;
display: block;
}
.users-table thead {
display: none;
}
.users-table tbody,
.users-table tr,
.users-table td {
display: block;
width: 100% !important;
box-sizing: border-box;
}
.users-table tr {
margin-bottom: 15px;
border: 1px solid #e0e0e0;
border-radius: var(--border-radius);
padding: 10px;
}
.users-table td {
padding: 8px 0;
border: none;
display: flex;
align-items: center;
word-wrap: break-word;
overflow-wrap: break-word;
}
.users-table td::before {
content: attr(data-label);
font-weight: bold;
width: 80px;
min-width: 80px;
margin-right: 10px;
color: #555;
}
.user-actions {
flex-direction: column;
gap: 8px;
width: 100%;
}
.user-actions .btn {
font-size: var(--font-size-xs);
padding: 8px 10px;
width: 100%;
text-align: center;
min-height: 36px;
line-height: 1.3;
}
.user-form .form-actions {
flex-direction: column;
}
/* Modal responsive */
.modal-content {
width: 95%;
max-height: 90vh;
}
.modal-body {
padding: var(--padding-base);
}
.form-row {
flex-direction: column;
gap: 10px;
}
.volunteer-item {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
.volunteer-actions {
align-self: flex-end;
}
.processing-actions {
flex-direction: column;
}
}
/* Very Small Screens (under 480px) */
@media (max-width: 480px) {
.admin-sidebar {
width: 260px;
left: -260px;
padding: 12px;
}
.admin-nav {
gap: 6px;
margin: 15px 0;
}
.admin-nav a {
padding: 8px 10px;
font-size: var(--font-size-sm);
}
.nav-icon {
font-size: var(--font-size-base);
margin-right: 6px;
}
.nav-text {
font-size: var(--font-size-sm);
}
.admin-content {
padding: 10px;
}
.admin-section {
padding: 10px;
}
.admin-sidebar {
padding: 10px;
}
.admin-nav a {
padding: 6px 10px;
font-size: var(--font-size-sm);
}
.header .header-actions .btn {
padding: 5px 8px;
font-size: var(--font-size-xs);
}
.admin-info {
display: none;
}
.admin-map {
height: 200px;
}
.walk-sheet-preview .walk-sheet-page {
transform: scale(0.25);
transform-origin: center top;
margin-bottom: -750px;
margin-left: auto;
margin-right: auto;
}
.form-group input,
.form-group select {
font-size: 16px; /* Prevent zoom on iOS */
}
.btn {
min-height: 44px;
padding: 10px 16px;
}
.btn-sm {
min-height: 36px;
padding: 8px 12px;
}
}
/* Ultra Small Screens (under 360px) */
@media (max-width: 360px) {
.admin-sidebar {
width: 240px;
left: -240px;
}
.admin-nav a {
padding: 8px;
font-size: var(--font-size-xs);
}
.nav-icon {
font-size: var(--font-size-sm);
margin-right: 5px;
}
.nav-text {
font-size: var(--font-size-xs);
}
}
/* Tablet Responsive (768px - 1024px) */
@media (max-width: 1024px) and (min-width: 769px) {
.admin-sidebar {
width: var(--sidebar-width-tablet);
min-width: var(--sidebar-width-tablet);
max-width: var(--sidebar-width-tablet);
padding: 15px;
}
.admin-content {
padding: var(--padding-base);
}
.admin-section {
padding: var(--padding-base);
}
.users-admin-container {
grid-template-columns: 1fr;
gap: var(--padding-base);
}
.shifts-admin-container {
grid-template-columns: 1fr;
gap: var(--padding-base);
}
.user-form,
.users-list {
padding: var(--padding-base);
}
.users-table {
font-size: var(--font-size-sm);
}
.users-table th,
.users-table td {
padding: 10px 8px;
}
}
/* Small Laptop (1024px - 1200px) */
@media (max-width: 1200px) and (min-width: 1025px) {
.admin-sidebar {
width: var(--sidebar-width-tablet);
min-width: var(--sidebar-width-tablet);
max-width: var(--sidebar-width-tablet);
}
.users-admin-container,
.shifts-admin-container {
grid-template-columns: 1fr 1.5fr;
gap: 25px;
}
.walk-sheet-container {
display: flex !important;
flex-direction: column !important;
gap: var(--padding-base);
}
.walk-sheet-config {
order: 1 !important;
margin-bottom: var(--padding-base);
}
.walk-sheet-preview {
order: 2 !important;
padding: var(--padding-base);
min-height: auto;
max-width: 100vw;
overflow-x: auto;
display: flex;
justify-content: center;
align-items: flex-start;
}
.walk-sheet-preview .walk-sheet-page {
transform: scale(0.75);
transform-origin: center top;
margin-bottom: -200px;
max-width: 100%;
margin-left: auto;
margin-right: auto;
}
}
@media (max-width: 1000px) {
.walk-sheet-preview .walk-sheet-page {
transform: scale(0.5);
transform-origin: center top;
margin-bottom: -400px;
margin-left: auto;
margin-right: auto;
}
.cuts-map-section {
height: 400px;
}
.cuts-filters {
grid-template-columns: 1fr;
}
}
/* Header Responsive */
@media (max-width: 1024px) {
.header {
padding: 10px 15px;
}
.header h1 {
font-size: var(--padding-base);
}
.header .header-actions {
gap: 8px;
}
}
/* Responsive Table for Very Small Screens */
@media (max-width: 640px) {
.users-table {
border: 0;
}
.users-table thead {
border: none;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
.users-table tr {
border-bottom: 3px solid #ddd;
display: block;
margin-bottom: 10px;
padding: 10px;
background: #f8f9fa;
border-radius: var(--border-radius);
}
.users-table td {
border: none;
border-bottom: 1px solid #eee;
display: block;
font-size: var(--font-size-sm);
text-align: right;
padding: 8px 10px;
}
.users-table td::before {
content: attr(data-label);
float: left;
font-weight: bold;
text-transform: uppercase;
font-size: 11px;
color: #666;
}
.users-table td:last-child {
border-bottom: 0;
}
.user-actions {
justify-content: flex-end;
margin-top: 8px;
}
.user-actions .btn {
width: auto !important;
min-width: 80px;
flex: none;
}
}

View File

@ -0,0 +1,189 @@
/* Status Messages and Notifications */
/* Toast notifications, alerts, and status indicators */
.status-container {
position: fixed;
top: 20px;
right: 20px;
z-index: var(--z-toast);
max-width: 400px;
pointer-events: none; /* Allow clicks to pass through container */
}
.status-message {
padding: 12px 16px;
margin-bottom: 10px;
border-radius: var(--border-radius);
font-size: var(--font-size-base);
animation: slideIn 0.3s ease-out;
pointer-events: auto; /* Allow interaction with individual messages */
cursor: pointer; /* Show it's clickable for dismissal */
}
.status-message.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.status-message.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.status-message.info {
background-color: #cce7ff;
color: #004085;
border: 1px solid #b3d7ff;
}
.status-message.warning {
background-color: #fff3cd;
color: #856404;
border: 1px solid #ffeeba;
}
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
/* QR Code Generation Status */
.qr-code-group.generating::after {
content: 'Generating QR Code...';
position: absolute;
top: 0;
right: 0;
font-size: var(--font-size-xs);
color: var(--primary-color);
background: white;
padding: 2px 8px;
border-radius: 3px;
box-shadow: var(--shadow-sm);
}
/* QR code stored indicator */
.qr-status {
font-size: var(--font-size-xs);
margin-top: 5px;
}
.qr-status.stored {
color: var(--success-color);
}
.qr-status.pending {
color: var(--warning-color);
}
/* Loading Messages */
.loading-message,
.empty-message {
text-align: center;
padding: 40px 20px;
color: #666;
font-style: italic;
background-color: #f8f9fa;
border-radius: var(--border-radius);
margin-top: 15px;
}
.loading-message {
background-color: #e3f2fd;
color: #1976d2;
}
/* Processing Status */
.processing-status {
padding: 1rem;
background: #f8f9fa;
border-radius: var(--border-radius);
text-align: center;
margin-bottom: 1.5rem;
animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse {
0% { opacity: 0.8; }
50% { opacity: 1; }
100% { opacity: 0.8; }
}
/* Progress Bars */
.progress-bar-container {
margin: 1.5rem 0;
}
.progress-bar {
height: 24px;
background: #e9ecef;
border-radius: 12px;
overflow: hidden;
}
.progress-bar-fill {
height: 100%;
background: linear-gradient(90deg, var(--primary-color) 0%, #45a049 100%);
transition: width 0.3s ease;
width: 0%;
}
.progress-text {
text-align: center;
margin-top: 0.5rem;
font-weight: 500;
}
/* Status Icons */
.status-icon {
display: inline-block;
width: 20px;
height: 20px;
line-height: 20px;
text-align: center;
border-radius: 50%;
font-weight: bold;
}
.status-icon.success {
background: var(--success-color);
color: white;
}
.status-icon.error {
background: var(--error-color);
color: white;
}
.status-icon.warning {
background: var(--warning-color);
color: #212529;
}
.status-icon.info {
background: var(--info-color);
color: white;
}
/* Spinner */
.progress-spinner {
display: inline-block;
width: 12px;
height: 12px;
border: 2px solid #f3f3f3;
border-top: 2px solid #6c757d;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}

View File

@ -0,0 +1,219 @@
/* User Management Interface Styles */
/* Tables, user forms, and user-related components */
.users-admin-container {
display: grid;
grid-template-columns: 1fr 2fr;
gap: 30px;
margin-top: var(--padding-base);
width: 100%;
box-sizing: border-box;
overflow: hidden;
}
.user-form,
.users-list {
background: white;
border-radius: var(--border-radius);
padding: 25px;
box-shadow: var(--shadow-sm);
border: 1px solid #e0e0e0;
width: 100%;
box-sizing: border-box;
}
.users-list {
overflow-x: auto;
min-width: 0;
}
.user-form h3,
.users-list h3 {
margin-bottom: var(--padding-base);
color: var(--dark-color);
border-bottom: 2px solid var(--primary-color);
padding-bottom: 10px;
font-size: var(--font-size-xl);
}
/* Users Table */
.users-table {
width: 100%;
border-collapse: collapse;
margin-top: 15px;
font-size: var(--font-size-base);
table-layout: auto;
min-width: 600px;
box-sizing: border-box;
}
.users-table th,
.users-table td {
padding: 12px;
text-align: left;
border-bottom: 1px solid #e0e0e0;
}
.users-table th {
background-color: #f8f9fa;
font-weight: 600;
color: var(--dark-color);
font-size: var(--font-size-sm);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.users-table tbody tr {
transition: background-color 0.2s;
}
.users-table tbody tr:hover {
background-color: #f8f9fa;
}
/* User Role Badges */
.user-role {
padding: 4px 8px;
border-radius: 12px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
display: inline-block;
text-align: center;
min-width: 45px;
line-height: 1.2;
}
.user-role.admin {
background-color: var(--error-color);
color: white;
}
.user-role.user {
background-color: var(--success-color);
color: white;
}
.user-role.temp {
background-color: var(--warning-color);
color: white;
}
/* User Expiration Information */
.expiration-info {
display: inline-block;
margin-left: 8px;
font-size: 0.8em;
color: #666;
background: #f5f5f5;
padding: 2px 6px;
border-radius: 3px;
}
.expiration-warning {
color: #ff5722;
font-weight: bold;
}
.expires-soon {
background-color: #fff3cd;
border-left: 4px solid var(--warning-color);
}
.expired {
background-color: #f8d7da;
border-left: 4px solid var(--error-color);
opacity: 0.7;
}
/* User Actions */
.user-actions {
display: flex;
gap: 8px;
}
.user-actions .btn {
padding: 6px 12px;
font-size: var(--font-size-xs);
border-radius: var(--border-radius);
font-weight: 500;
}
/* Users List Header */
.users-list-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--padding-base);
padding-bottom: 10px;
border-bottom: 2px solid #eee;
}
.users-list-header h3 {
margin: 0;
color: var(--dark-color);
}
/* Helper Text */
.help-text {
font-size: 0.85em;
color: #666;
margin-top: 4px;
display: block;
}
/* Volunteer Management */
.volunteer-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 15px;
background-color: #f8f9fa;
border-radius: var(--border-radius);
margin-bottom: 8px;
}
.volunteer-info {
flex: 1;
}
.volunteer-name {
font-weight: bold;
color: var(--dark-color);
}
.volunteer-email {
font-size: var(--font-size-sm);
color: #666;
margin-top: 2px;
}
.volunteer-actions {
display: flex;
gap: 8px;
}
.no-volunteers {
text-align: center;
color: #666;
font-style: italic;
padding: var(--padding-base);
background-color: #f8f9fa;
border-radius: var(--border-radius);
}
/* User Select Dropdown */
#user-select {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: var(--border-radius);
font-size: var(--font-size-base);
background-color: white;
}
#user-select:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.1);
}

View File

@ -0,0 +1,66 @@
/* CSS Variables - Admin Panel Theme Configuration */
/* Centralized theme variables for consistency across admin modules */
:root {
/* Colors */
--primary-color: #4CAF50;
--secondary-color: #6c757d;
--success-color: #28a745;
--error-color: #dc3545;
--warning-color: #ffc107;
--info-color: #17a2b8;
--dark-color: #333;
--light-color: #f5f5f5;
/* Layout */
--header-height: 60px;
--sidebar-width: 250px;
--sidebar-width-tablet: 220px;
--sidebar-width-mobile: 280px;
--app-height: 100vh;
/* Spacing */
--border-radius: 6px;
--transition: all 0.2s ease;
--padding-base: 20px;
--padding-sm: 15px;
--padding-xs: 10px;
/* Typography */
--font-size-base: 14px;
--font-size-sm: 13px;
--font-size-xs: 12px;
--font-size-lg: 16px;
--font-size-xl: 18px;
/* Shadows */
--shadow-sm: 0 2px 4px rgba(0,0,0,0.1);
--shadow-md: 0 4px 8px rgba(0,0,0,0.15);
--shadow-lg: 0 8px 24px rgba(0,0,0,0.15);
/* Z-index layers */
--z-dropdown: 1000;
--z-sticky: 1020;
--z-fixed: 1030;
--z-modal-backdrop: 1040;
--z-modal: 1050;
--z-popover: 1060;
--z-tooltip: 1070;
--z-toast: 12300;
}
/* Theme color variations */
.theme-green {
--primary-color: #4CAF50;
--primary-hover: #45a049;
}
.theme-blue {
--primary-color: #007bff;
--primary-hover: #0056b3;
}
.theme-purple {
--primary-color: #6f42c1;
--primary-hover: #5a2d91;
}

View File

@ -0,0 +1,302 @@
/* Walk Sheet Configuration and Preview Styles */
/* QR codes, form layouts, and printable walk sheet formatting */
.walk-sheet-container {
display: grid;
grid-template-columns: 2fr 3fr;
gap: 30px;
margin-top: var(--padding-base);
align-items: flex-start;
}
.walk-sheet-config {
background-color: #f9f9f9;
padding: var(--padding-base);
border-radius: var(--border-radius);
}
.walk-sheet-config h3 {
font-size: var(--font-size-lg);
margin-bottom: 15px;
color: var(--dark-color);
}
/* QR Code Groups */
.qr-code-group {
background-color: white;
padding: 15px;
border-radius: var(--border-radius);
margin-bottom: 15px;
border: 1px solid #e0e0e0;
position: relative;
}
.qr-code-group h4 {
font-size: var(--font-size-base);
margin-bottom: 10px;
color: #666;
}
/* Walk Sheet Preview */
.walk-sheet-preview {
background-color: #f5f5f5;
padding: 30px;
border-radius: var(--border-radius);
box-shadow: var(--shadow-lg);
min-width: 350px;
max-width: 100%;
margin: 0 auto;
min-height: 950px;
display: flex;
flex-direction: column;
align-items: center;
overflow: auto;
}
.walk-sheet-preview h3 {
font-size: var(--font-size-lg);
margin-bottom: 10px;
color: var(--dark-color);
}
.preview-controls {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.preview-info {
font-size: var(--font-size-xs);
color: #666;
}
/* Walk Sheet Page - Actual Paper Size */
.walk-sheet-page {
width: 816px; /* 8.5 inches * 96 DPI */
height: 1056px; /* 11 inches * 96 DPI */
background: white;
box-shadow: var(--shadow-lg);
padding: 48px; /* 0.5 inch margins */
margin: 0 auto;
overflow: hidden;
font-size: 12pt;
line-height: 1.6;
position: relative;
transform: none;
box-sizing: border-box;
}
/* Walk Sheet Content Styles */
.ws-header {
text-align: center;
margin-bottom: 30px;
border-bottom: 3px solid #333;
padding-bottom: 15px;
}
.ws-title {
font-size: 24pt;
font-weight: bold;
margin: 0;
color: #000;
}
.ws-subtitle {
font-size: 12pt;
color: #444;
margin: 8px 0 0 0;
}
.ws-qr-section {
display: flex;
justify-content: space-around;
margin: 30px 0;
padding: var(--padding-base);
background-color: #f5f5f5;
border-radius: 8px;
border: 1px solid #ddd;
}
.ws-qr-item {
text-align: center;
}
.ws-qr-code {
margin-bottom: 5px;
}
.ws-qr-code img,
.ws-qr-code canvas {
display: block;
margin: 0 auto;
width: 120px;
height: 120px;
image-rendering: crisp-edges;
image-rendering: -webkit-crisp-edges;
image-rendering: pixelated;
image-rendering: -moz-crisp-edges;
}
.ws-qr-label {
font-size: 11pt;
font-weight: bold;
margin-top: 10px;
color: #333;
}
/* Walk Sheet Form Sections */
.ws-form-section {
margin-top: 30px;
}
.ws-form-row {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: var(--padding-base);
margin-bottom: 18px;
}
.ws-form-group {
border-bottom: 2px solid #ccc;
padding-bottom: 8px;
}
.ws-form-label {
font-size: 10pt;
color: #555;
display: block;
margin-bottom: 5px;
font-weight: 500;
}
.ws-form-field {
height: 30px;
background-color: #fafafa;
border: 1px solid #eee;
}
.ws-form-field.circles {
height: auto;
background: none;
border: none;
display: flex;
gap: 15px;
align-items: center;
padding: 5px 0;
}
.ws-circle-option {
display: flex;
align-items: center;
gap: 5px;
font-size: 11pt;
}
.ws-circle {
width: 24px;
height: 24px;
border: 2px solid #333;
border-radius: 50%;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 10pt;
font-weight: 500;
background-color: white;
}
/* Notes Section */
.ws-notes-section {
margin-top: 30px;
}
.ws-notes-label {
font-size: 11pt;
font-weight: bold;
margin-bottom: 10px;
color: #333;
}
.ws-notes-area {
width: 100%;
height: 80px;
border: 2px solid #ccc;
background-color: #fafafa;
border-radius: 4px;
}
/* Walk Sheet Footer */
.ws-footer {
position: absolute;
bottom: 48px;
left: 48px;
right: 48px;
text-align: center;
font-size: 10pt;
color: #666;
padding-top: 15px;
border-top: 1px solid #ccc;
max-height: 80px;
overflow: hidden;
line-height: 1.4;
}
/* Print Styles */
@media print {
body * {
visibility: hidden;
}
.walk-sheet-page, .walk-sheet-page * {
visibility: visible;
}
.walk-sheet-page {
position: fixed;
left: 0;
top: 0;
width: 8.5in;
height: 11in;
max-width: none;
padding: 0.5in;
margin: 0;
box-shadow: none;
font-size: 12pt;
page-break-after: always;
}
.ws-qr-code img {
width: 100px !important;
height: 100px !important;
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
}
/* Container Adjustments */
.walk-sheet-container {
display: grid;
grid-template-columns: minmax(300px, 400px) 1fr;
gap: 30px;
margin-top: var(--padding-base);
align-items: flex-start;
}
/* Large Screen Optimizations */
@media (min-width: 1600px) {
.walk-sheet-container {
gap: 40px;
grid-template-columns: 1fr 2fr;
}
.walk-sheet-preview {
max-width: 100%;
min-height: 1100px;
padding: 40px;
}
.walk-sheet-preview .walk-sheet-page {
transform: none;
}
}

View File

@ -103,7 +103,7 @@ class ClientCacheManager {
* Start periodic version checking
*/
startVersionChecking() {
// Check every 30 seconds
// Check every 60 seconds (reduced from 30 to ease rate limiting)
this.versionCheckInterval = setInterval(async () => {
try {
if (await this.hasVersionChanged()) {
@ -111,8 +111,23 @@ class ClientCacheManager {
}
} catch (error) {
console.warn('Version check failed:', error);
// If we get a rate limit error, slow down checks
if (error.message?.includes('429') || error.message?.includes('Too Many Requests')) {
console.log('Slowing down version checks due to rate limiting');
clearInterval(this.versionCheckInterval);
// Restart with a longer interval (2 minutes)
this.versionCheckInterval = setInterval(async () => {
try {
if (await this.hasVersionChanged()) {
this.handleVersionChange();
}
} catch (error) {
console.warn('Version check failed:', error);
}
}, 120000); // 2 minutes
}
}
}, 30000);
}, 60000); // 1 minute
}
/**

View File

@ -70,14 +70,42 @@ function setupAutoRefresh() {
// Use longer interval for temp users to avoid rate limiting
const refreshInterval_ms = currentUser?.userType === 'temp' ?
120000 : // 2 minutes for temp users
CONFIG.REFRESH_INTERVAL; // 30 seconds for regular users
60000; // 1 minute for regular users (increased from 30 seconds)
refreshInterval = setInterval(() => {
loadLocations();
let consecutiveErrors = 0;
const maxErrors = 3;
refreshInterval = setInterval(async () => {
try {
await loadLocations();
consecutiveErrors = 0; // Reset error count on success
} catch (error) {
consecutiveErrors++;
console.warn(`Location refresh failed (${consecutiveErrors}/${maxErrors}):`, error);
// If we've had too many consecutive errors, slow down the refresh
if (consecutiveErrors >= maxErrors) {
console.log('Too many consecutive errors, slowing down refresh interval');
clearInterval(refreshInterval);
// Restart with a longer interval
const slowInterval = refreshInterval_ms * 2; // Double the interval
refreshInterval = setInterval(async () => {
try {
await loadLocations();
consecutiveErrors = 0;
} catch (error) {
console.warn('Location refresh failed:', error);
}
}, slowInterval);
}
}
}, refreshInterval_ms);
if (currentUser?.userType === 'temp') {
console.log('Auto-refresh set to 2 minutes for temporary account');
} else {
console.log('Auto-refresh set to 1 minute for regular account');
}
}).catch(error => {
// Fallback to default interval if auth module fails to load

View File

@ -145,10 +145,7 @@ app.use(cacheBusting.staticCacheMiddleware());
// Serve static files with proper cache headers
app.use(express.static('public'));
// Apply rate limiting to API routes
app.use('/api/', apiLimiter);
// Cache busting version endpoint
// Cache busting version endpoint (before rate limiting)
app.get('/api/version', (req, res) => {
res.json({
version: cacheBusting.getVersion(),
@ -156,6 +153,9 @@ app.get('/api/version', (req, res) => {
});
});
// Apply rate limiting to API routes
app.use('/api/', apiLimiter);
// Proxy endpoint for MkDocs search
app.get('/api/docs-search', async (req, res) => {
try {

View File

@ -186,7 +186,51 @@ Admin panel HTML page for managing start location, walk sheet, shift management,
# app/public/css/admin.css
CSS styles specific to the admin panel UI.
Main entry point for admin panel CSS with modular imports. Refactored from a single large file into focused modules for better maintainability. Imports all admin-specific CSS modules including variables, layout, forms, user management, walk sheets, and responsive design.
# app/public/css/admin/variables.css
CSS custom properties and theme configuration for the admin panel. Defines colors, spacing, typography, shadows, and z-index layers used consistently across all admin modules.
# app/public/css/admin/layout.css
Layout components for the admin panel including container structure, sidebar navigation, content areas, map controls, and mobile menu components. Handles the overall admin interface layout and navigation.
# app/public/css/admin/forms.css
Form and button styles for the admin interface. Includes form field styling, button variations (primary, secondary, danger, etc.), form actions, and special form field types like circles and validation states.
# app/public/css/admin/status-messages.css
Status messages, notifications, and progress indicators for the admin panel. Handles toast notifications, progress bars, loading states, QR code generation status, and various status icons with animations.
# app/public/css/admin/user-management.css
User management interface styles including user tables, role badges, expiration indicators, volunteer management components, and user form styling. Handles the complete user administration interface.
# app/public/css/admin/walk-sheet.css
Walk sheet configuration and preview styles. Handles QR code display, printable walk sheet layout (8.5x11 paper), form field circles, notes sections, and print-specific styling for walk sheet generation.
# app/public/css/admin/data-convert.css
Data conversion interface styles for CSV upload and processing. Includes file upload areas, processing progress indicators, results preview tables, drag-and-drop styling, and map integration for geocoding results display.
# app/public/css/admin/nocodb-links.css
External database integration card styles for NocoDB connections. Provides styling for integration status cards, connection indicators, information boxes, and database management interface components.
# app/public/css/admin/cuts-shifts.css
Geographic cuts and shift management interface styles. Handles cut drawing controls, form states, shift scheduling interface, map interaction components, and administrative controls for cuts and volunteer shifts.
# app/public/css/admin/modals.css
Modal dialogs and email composition interface for the admin panel. Includes modal structure, email composition with rich text editing, progress tracking for email campaigns, and interactive dialog components.
# app/public/css/admin/responsive.css
Responsive design and media queries for all admin panel components. Handles mobile, tablet, and desktop layout adaptations, touch-friendly interfaces, mobile navigation, and responsive table layouts.
# app/public/css/modules/apartment-marker.css