diff --git a/map/Instuctions.md b/map/Instuctions.md new file mode 100644 index 0000000..44fb4bb --- /dev/null +++ b/map/Instuctions.md @@ -0,0 +1,10 @@ +# Instructions + +The following are instructions for developing this project. The project is called Map and is a canvasing application for political campaigns. + +It uses nocodb as a backend database. + +## Rules + +- Do not use inline event handlers. Use properly attached event listeners instead. +- Always update the README.md, or instruct the user to update the README, when developing new features. diff --git a/map/README.md b/map/README.md index 18a7891..b26aaa8 100644 --- a/map/README.md +++ b/map/README.md @@ -15,7 +15,10 @@ A containerized web application that visualizes geographic data from NocoDB on a - 🎯 Configurable map start location - 📋 Walk Sheet generator for door-to-door canvassing - 🔗 QR code integration for digital resources -- 🐳 Docker containerization for easy deployment +- � Volunteer shift management system +- ✋ User shift signup and cancellation +- 👥 Admin shift creation and management +- �🐳 Docker containerization for easy deployment - 🆓 100% open source (no proprietary dependencies) ## Quick Start @@ -40,13 +43,15 @@ A containerized web application that visualizes geographic data from NocoDB on a Edit the `.env` file with your NocoDB API and API Url: ```env # NocoDB API Configuration - NOCODB_API_URL=https://your-nocodb-instance.com/api/v1 + NOCODB_API_URL=https://db.cmlite.org/api/v1 NOCODB_API_TOKEN=your-api-token-here # These will be populated after running build-nocodb.sh NOCODB_VIEW_URL= NOCODB_LOGIN_SHEET= NOCODB_SETTINGS_SHEET= + NOCODB_SHIFTS_SHEET= + NOCODB_SHIFT_SIGNUPS_SHEET= # Server Configuration PORT=3000 @@ -57,6 +62,13 @@ A containerized web application that visualizes geographic data from NocoDB on a DEFAULT_LAT=53.5461 DEFAULT_LNG=-113.4938 DEFAULT_ZOOM=11 + + # Cloudflare Settings + TRUST_PROXY=true + COOKIE_DOMAIN=.cmlite.org + + # Allowed Origins + ALLOWED_ORIGINS=https://map.cmlite.org,http://localhost:3000 ``` 3. **Auto-Create Database Structure** @@ -67,26 +79,30 @@ A containerized web application that visualizes geographic data from NocoDB on a ./build-nocodb.sh ``` - This creates three tables: + This creates five tables: - **Locations** - Main map data with geo-location, contact info, support levels - **Login** - User authentication (email, name, admin flag) - **Settings** - Admin configuration and QR codes + - **Shifts** - Shift scheduling and management + - **Shift Signups** - User shift registrations 4. **Get Table URLs** After the script completes: - 1. Login to your NocoDB instance - 2. Navigate to your project ("Map Viewer Project") + 1. Login to your NocoDB instance at https://db.cmlite.org + 2. Navigate to your project ("Map Viewer Project - TIMESTAMP") 3. Copy the view URLs for each table from your browser address bar - 4. URLs should look like: `https://your-nocodb.com/dashboard/#/nc/project-id/table-id` + 4. URLs should look like: `https://db.cmlite.org/dashboard/#/nc/project-id/table-id` 5. **Update Environment with URLs** Edit your `.env` file and add the table URLs: ```env - NOCODB_VIEW_URL=https://your-nocodb.com/dashboard/#/nc/project-id/locations-table-id - NOCODB_LOGIN_SHEET=https://your-nocodb.com/dashboard/#/nc/project-id/login-table-id - NOCODB_SETTINGS_SHEET=https://your-nocodb.com/dashboard/#/nc/project-id/settings-table-id + NOCODB_VIEW_URL=https://db.cmlite.org/dashboard/#/nc/pnsalzrup2zqvz8/m6g7bkzv7s1w2ur + NOCODB_LOGIN_SHEET=https://db.cmlite.org/dashboard/#/nc/pnsalzrup2zqvz8/mizyc64e4r7ppzh + NOCODB_SETTINGS_SHEET=https://db.cmlite.org/dashboard/#/nc/pnsalzrup2zqvz8/mix06f2mlep7gqb + NOCODB_SHIFTS_SHEET=https://db.cmlite.org/dashboard/#/nc/pnsalzrup2zqvz8/mkx0tex0iquus1u + NOCODB_SHIFT_SIGNUPS_SHEET=https://db.cmlite.org/dashboard/#/nc/pnsalzrup2zqvz8/mi8jg1tn26mu8fj ``` 6. **Build and Deploy** @@ -105,6 +121,8 @@ A containerized web application that visualizes geographic data from NocoDB on a - Check container status: `docker-compose ps` - View logs: `docker-compose logs -f map-viewer` - Access the application at: http://localhost:3000 + - Access shift management at: http://localhost:3000/shifts.html + - Access admin panel at: http://localhost:3000/admin.html (admin users only) ## Database Schema @@ -147,6 +165,18 @@ The build script automatically creates the following table structure: - `qr_code_2_image` (Attachment): QR code 2 image - `qr_code_3_image` (Attachment): QR code 3 image +### Shifts Table +- Standard NocoDB fields for shift scheduling and management +- Contains shift dates, times, locations, capacity limits +- Status tracking (Active, Cancelled, Full) +- Created automatically by build script with basic structure + +### Shift Signups Table +- Links users to shifts they've signed up for +- Tracks signup timestamps and user information +- Handles cancellations and waitlist management +- Created automatically by build script with basic structure + ## API Endpoints ### Public Endpoints @@ -159,6 +189,20 @@ The build script automatically creates the following table structure: - `GET /api/config/start-location` - Get map start location - `GET /health` - Health check +### Shifts Endpoints (requires authentication) + +- `GET /api/shifts` - Get all available shifts +- `GET /api/shifts/my-signups` - Get current user's shift signups +- `POST /api/shifts/:shiftId/signup` - Sign up for a shift +- `POST /api/shifts/:shiftId/cancel` - Cancel shift signup + +### Shifts Admin Endpoints (requires admin privileges) + +- `GET /api/shifts/admin` - Get all shifts including cancelled ones +- `POST /api/shifts/admin` - Create new shift +- `PUT /api/shifts/admin/:id` - Update existing shift +- `DELETE /api/shifts/admin/:id` - Delete shift + ### Authentication Endpoints - `POST /api/auth/login` - User login @@ -172,6 +216,35 @@ The build script automatically creates the following table structure: - `GET /api/admin/walk-sheet-config` - Get walk sheet configuration - `POST /api/admin/walk-sheet-config` - Save walk sheet configuration +## Shifts Management + +The application includes a comprehensive volunteer shift management system accessible at `/shifts.html`. + +### User Features + +- **View Available Shifts**: See all upcoming shifts with date, time, and capacity information +- **Sign Up for Shifts**: One-click signup for available shifts +- **My Shifts Dashboard**: View all your current shift signups +- **Cancel Signups**: Cancel your shift signups when needed +- **Date Filtering**: Filter shifts by specific dates +- **Real-time Updates**: Shift availability updates dynamically + +### Admin Features + +Administrators have additional capabilities for managing shifts: + +- **Create New Shifts**: Add new volunteer shifts with date, time, location, and capacity +- **Edit Existing Shifts**: Modify shift details, times, or capacity +- **Cancel Shifts**: Mark shifts as cancelled (they remain in system but hidden from users) +- **View All Signups**: See who has signed up for each shift +- **Manage Capacity**: Set maximum number of volunteers per shift + +### Shift Status System + +- **Active**: Available for signups +- **Full**: Capacity reached, no more signups accepted +- **Cancelled**: Hidden from public view but retained in database + ## Admin Panel Users with admin privileges can access the admin panel at `/admin.html` to configure system settings. @@ -216,13 +289,20 @@ All configuration is done via environment variables: |----------|-------------|---------| | `NOCODB_API_URL` | NocoDB API base URL | Required | | `NOCODB_API_TOKEN` | API authentication token | Required | -| `NOCODB_VIEW_URL` | Full NocoDB view URL | Required | +| `NOCODB_VIEW_URL` | Full NocoDB view URL for locations table | Required | | `NOCODB_LOGIN_SHEET` | Login table URL for authentication | Required | -| `NOCODB_SETTINGS_SHEET` | Settings table URL for admin config | Optional | +| `NOCODB_SETTINGS_SHEET` | Settings table URL for admin config | Required | +| `NOCODB_SHIFTS_SHEET` | Shifts table URL for shift management | Required | +| `NOCODB_SHIFT_SIGNUPS_SHEET` | Shift signups table URL for user registrations | Required | | `PORT` | Server port | 3000 | +| `NODE_ENV` | Environment mode | production | +| `SESSION_SECRET` | Session encryption secret | Required | | `DEFAULT_LAT` | Default map latitude | 53.5461 | | `DEFAULT_LNG` | Default map longitude | -113.4938 | | `DEFAULT_ZOOM` | Default map zoom level | 11 | +| `TRUST_PROXY` | Trust proxy headers (for Cloudflare) | true | +| `COOKIE_DOMAIN` | Cookie domain setting | .cmlite.org | +| `ALLOWED_ORIGINS` | CORS allowed origins | Multiple URLs | ## Maintenance Commands diff --git a/map/app/controllers/shiftsController.js b/map/app/controllers/shiftsController.js index 51d37f2..fe6f364 100644 --- a/map/app/controllers/shiftsController.js +++ b/map/app/controllers/shiftsController.js @@ -16,18 +16,40 @@ class ShiftsController { logger.info('Loading public shifts from:', config.nocodb.shiftsSheetId); - // Load all shifts without filter - we'll filter in JavaScript const response = await nocodbService.getAll(config.nocodb.shiftsSheetId, { sort: 'Date,Start Time' }); - logger.info('Loaded shifts:', response); - - // Filter out cancelled shifts manually - const shifts = (response.list || []).filter(shift => + let shifts = (response.list || []).filter(shift => shift.Status !== 'Cancelled' ); + // If signups sheet is configured, calculate current volunteer counts + if (config.nocodb.shiftSignupsSheetId) { + try { + const signupsResponse = await nocodbService.getAll(config.nocodb.shiftSignupsSheetId); + const allSignups = signupsResponse.list || []; + + // Update each shift with calculated volunteer count + shifts = shifts.map(shift => { + const confirmedSignups = allSignups.filter(signup => + signup['Shift ID'] === shift.ID && signup.Status === 'Confirmed' + ); + + const currentVolunteers = confirmedSignups.length; + const maxVolunteers = shift['Max Volunteers'] || 0; + + return { + ...shift, + 'Current Volunteers': currentVolunteers, + 'Status': currentVolunteers >= maxVolunteers ? 'Full' : 'Open' + }; + }); + } catch (signupError) { + logger.warn('Could not load signups for volunteer count calculation:', signupError); + } + } + res.json({ success: true, shifts: shifts diff --git a/map/app/public/css/shifts.css b/map/app/public/css/shifts.css index 8e8dd88..1247f33 100644 --- a/map/app/public/css/shifts.css +++ b/map/app/public/css/shifts.css @@ -109,6 +109,10 @@ .shift-actions { margin-top: 15px; + display: flex; + gap: 10px; + flex-wrap: wrap; + align-items: center; } .no-shifts { @@ -118,14 +122,67 @@ grid-column: 1 / -1; /* Span all columns */ } -/* Tablet: 2 columns */ -@media (max-width: 1024px) and (min-width: 769px) { - .shifts-grid { - grid-template-columns: repeat(2, 1fr); - } +/* Calendar dropdown styles */ +.calendar-dropdown { + position: relative; + display: inline-block; } -/* Mobile: 1 column */ +.calendar-toggle { + cursor: pointer; + position: relative; +} + +.calendar-options { + position: absolute; + top: 100%; + left: 0; + min-width: 180px; + background: white; + border: 1px solid #e0e0e0; + border-radius: var(--border-radius); + box-shadow: 0 4px 12px rgba(0,0,0,0.15); + z-index: 1000; + margin-top: 4px; +} + +.calendar-option { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 15px; + text-decoration: none; + color: var(--dark-color); + transition: var(--transition); + border-bottom: 1px solid #f0f0f0; +} + +.calendar-option:last-child { + border-bottom: none; +} + +.calendar-option:hover { + background-color: #f5f5f5; +} + +.calendar-option img { + width: 16px; + height: 16px; +} + +/* Ensure dropdowns appear above other elements */ +.shift-card { + position: relative; +} + +/* Update signup actions for My Shifts section */ +.signup-actions { + display: flex; + gap: 10px; + align-items: center; +} + +/* Mobile adjustments */ @media (max-width: 768px) { .shifts-container { padding: 15px; @@ -142,8 +199,30 @@ gap: 10px; } - .shift-card { - padding: 15px; + .signup-actions { + width: 100%; + justify-content: flex-start; + } + + .shift-actions { + flex-direction: column; + width: 100%; + } + + .shift-actions .btn, + .calendar-dropdown { + width: 100%; + } + + .calendar-toggle { + width: 100%; + justify-content: center; + } + + .calendar-options { + left: 0; + right: 0; + width: 100%; } .my-signups { @@ -158,4 +237,13 @@ .filter-group { flex-wrap: wrap; } +} + +/* Prevent dropdown from being cut off */ +.shifts-grid { + overflow: visible; +} + +.shift-card { + overflow: visible; } \ No newline at end of file diff --git a/map/app/public/js/shifts.js b/map/app/public/js/shifts.js index 8d802e1..08f3fd0 100644 --- a/map/app/public/js/shifts.js +++ b/map/app/public/js/shifts.js @@ -105,7 +105,8 @@ function displayShifts(shifts) { ${shift.Description ? `
📅 ${shiftDate.toLocaleDateString()} ⏰ ${signup.shift['Start Time']} - ${signup.shift['End Time']}