feat: configurable icons from Docker share (#61)

* docs: add design spec for configurable icons from Docker share

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: add implementation plan for configurable icons from Docker share

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Remove icons

* feat: serve icons from config/icons/ Docker share via static file middleware

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* update readme

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Claudio Schaad 2026-04-19 17:03:27 +02:00 committed by GitHub
parent d00f5ea8b7
commit 7935dd8e15
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 299 additions and 185 deletions

View file

@ -11,40 +11,4 @@
<ProjectReference Include="..\Services\ShiftScheduler.Services.csproj" /> <ProjectReference Include="..\Services\ShiftScheduler.Services.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Content Update="wwwroot\break-icon.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="wwwroot\icons\break-icon.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="wwwroot\icons\G2.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="wwwroot\icons\Bg.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="wwwroot\icons\F1.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="wwwroot\icons\G1.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="wwwroot\icons\G2TS.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="wwwroot\icons\Kompensation.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="wwwroot\icons\BB.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="wwwroot\icons\G1TS.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="wwwroot\icons\S1.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project> </Project>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 B

207
README.md
View file

@ -1,75 +1,59 @@
# ShiftScheduler # ShiftScheduler
A modern Blazor WebAssembly application for managing work shift schedules with calendar export capabilities and transport integration. A Blazor WebAssembly application for managing work shift schedules with calendar export and transport integration.
## Overview ## Overview
ShiftScheduler is a web-based shift management system that allows users to: ShiftScheduler is a web-based shift management system that allows users to:
- Plan and visualize work shifts using an intuitive calendar interface - Plan and visualize work shifts using a monthly calendar interface
- Export schedules to industry-standard formats (ICS and PDF) - Export schedules to ICS and PDF formats
- Calculate optimal transport connections for work commutes - Sync shifts directly to Google Calendar or Nextcloud Calendar
- Manage multiple shift types with customizable icons and time periods - View optimal transport connections for work commutes
- Secure access through Google OAuth authentication - Secure access through Google OAuth authentication
Perfect for individuals or small teams who need to organize shift work, track schedules, and integrate with existing calendar systems.
## Key Features ## Key Features
### 📅 **Calendar-Based Shift Management** ### Calendar-Based Shift Management
- Interactive monthly calendar view with easy shift selection - Monthly calendar view with day-by-day shift assignment
- Switch between current and next month - Toggle between current and next month
- Visual shift indicators with customizable icons and colors - Mobile-friendly week layout alongside the desktop grid view
- Responsive design for desktop and mobile devices
### 🔄 **Export Capabilities** ### Export & Sync
- **ICS Export**: Import schedules directly into Google Calendar, Outlook, or any calendar application - **ICS Export**: Download a calendar file compatible with any calendar application
- **PDF Export**: Generate printable shift schedules with professional formatting - **PDF Export**: Generate a printable monthly schedule
- **Google Calendar Sync**: Direct synchronization with Google Calendar (NEW!) - **Google Calendar Sync**: Direct sync to a selected Google Calendar; existing app-managed events are updated, other events are untouched
- Automatic calendar selection for multiple calendars - **Nextcloud Calendar Sync**: CalDAV-based sync to a selected Nextcloud calendar
- Smart event management (updates existing app-created events)
- Respects existing calendar events from other sources
- One-click export functionality
### 🚆 **Transport Integration** ### Transport Integration
- Automatic calculation of travel times between configurable stations - Calculates optimal departure and arrival times for work commutes
- Integration with Swiss public transport API (transport.opendata.ch) - Integrates with the Swiss public transport API (`transport.opendata.ch`)
- Safety buffer calculations for reliable commute planning - Displays morning and afternoon connections alongside each shift
- Display of optimal departure and arrival times - Configurable safety buffers and break durations
### 🔐 **Secure Authentication** ### Authentication
- Google OAuth integration for secure access - Google OAuth 2.0 with a configurable list of authorized email addresses
- Configurable authorized email addresses
- Session management with proper login/logout functionality
### ⚙️ **Flexible Configuration** ### Configuration
- **Shift Types**: Fully customizable shift definitions with: - Shift definitions (name, emoji icon, morning/afternoon time ranges) managed via the in-app configuration dialog
- Custom names and icons (emoji or PNG images) - Transport settings (stations, API URL, timing buffers) configurable in-app
- Morning and afternoon time periods - Configuration can be exported and imported as JSON for backup
- Flexible time format support
- **Transport Settings**: Configurable start/end stations, API parameters, and timing buffers
- **Persistent Configuration**: Settings survive application restarts and container rebuilds
### 🐳 **Docker Ready** ## Default Shift Types
- Container-friendly with persistent external configuration
- Easy deployment with Docker Compose
- Configuration backup and version control support
## Available Shift Types (Default Configuration) | Icon | Name | Morning Time | Afternoon Time |
|------|------|--------------|----------------|
| ⚫ | Frei | — | — |
| 🌴 | Urlaub | — | — |
| 🌅 | Früh | 06:00–14:00 | — |
| 🌆 | Spät | — | 14:00–22:00 |
| ☀️ | Tag | 08:00–12:00 | 13:00–17:00 |
| (icon) | Pause | 10:00–10:15 | 15:00–15:15 |
| Icon | Name | Morning Time | Afternoon Time | Description | All shift types are configurable through the in-app settings dialog.
|------|------|--------------|----------------|-------------|
| ⚫ | Frei | - | - | Free day / Day off |
| 🌴 | Urlaub | - | - | Vacation day |
| 🛑 | Pause | 10:00-10:15 | 15:00-15:15 | Break periods |
| 🌅 | Früh | 06:00-14:00 | - | Early shift |
| 🌆 | Spät | - | 14:00-22:00 | Late shift |
| ☀️ | Tag | 08:00-12:00 | 13:00-17:00 | Day shift |
*All shift types are fully configurable through the application interface.* ## Configuration
## Configuration Options ### Authentication (`Server/appsettings.json`)
### Authentication Settings
```json ```json
{ {
"Authentication": { "Authentication": {
@ -78,14 +62,24 @@ Perfect for individuals or small teams who need to organize shift work, track sc
"ClientSecret": "your-google-client-secret" "ClientSecret": "your-google-client-secret"
}, },
"AuthorizedEmails": [ "AuthorizedEmails": [
"user1@gmail.com", "user@gmail.com"
"user2@example.com"
] ]
} }
} }
``` ```
### Transport Configuration ### Nextcloud CalDAV
```json
{
"Nextcloud": {
"BaseUrl": "https://your-nextcloud-instance.example.com",
"Username": "your-username",
"AppPassword": "your-app-password"
}
}
```
### Transport
```json ```json
{ {
"Transport": { "Transport": {
@ -101,26 +95,11 @@ Perfect for individuals or small teams who need to organize shift work, track sc
} }
``` ```
### Shift Definitions
```json
{
"Shifts": [
{
"Name": "Custom Shift",
"Icon": "🎯",
"MorningTime": "09:00-13:00",
"AfternoonTime": "14:00-18:00"
}
]
}
```
## Installation & Setup ## Installation & Setup
### Prerequisites ### Prerequisites
- .NET 9.0 SDK - .NET 9.0 SDK
- Modern web browser - Google Cloud Console account (for OAuth)
- Google Cloud Console account (for authentication)
### Quick Start ### Quick Start
@ -135,10 +114,9 @@ Perfect for individuals or small teams who need to organize shift work, track sc
dotnet restore dotnet restore
``` ```
3. **Configure authentication** (see [Authentication Setup Guide](authentication-setup.md)) 3. **Configure authentication**
- Create Google OAuth application - See [Authentication Setup Guide](authentication-setup.md)
- Update `Server/appsettings.json` with your credentials - Update `Server/appsettings.json` with your Google OAuth credentials and authorized emails
- Add authorized email addresses
4. **Build and run** 4. **Build and run**
```bash ```bash
@ -147,84 +125,15 @@ Perfect for individuals or small teams who need to organize shift work, track sc
dotnet run dotnet run
``` ```
5. **Access the application** 5. **Open the app**
- Navigate to `http://localhost:5000` - Navigate to `http://localhost:5000`
- Sign in with your Google account - Sign in with your Google account
- Start planning your shifts!
### Docker Deployment
```yaml
version: '3.8'
services:
shiftscheduler:
build: .
ports:
- "5000:5000"
volumes:
- ./config:/app/config # Persistent configuration
environment:
- ASPNETCORE_ENVIRONMENT=Production
```
See [Docker Configuration Guide](DOCKER_CONFIG.md) for detailed deployment instructions.
## Usage
### Planning Shifts
1. Navigate between months using the month selection buttons
2. Click on any day to cycle through available shift types
3. Shifts are automatically saved and persist across sessions
4. Hover over shifts to see detailed time information
### Exporting Schedules
- **ICS Export**: Click "Export to ICS" to download a calendar file compatible with all major calendar applications
- **Google Calendar Sync**: Click "Sync to Google Calendar" to directly sync shifts to your Google Calendar
- If you have multiple calendars, choose which one to sync to
- Existing shift events will be updated automatically
- Other calendar events remain untouched
- **PDF Export**: Click "Export to PDF" to generate a printable schedule document
### Managing Configuration
1. Click the "⚙️ Configuration" button to access settings
2. **Shift Management**: Add, edit, or remove shift types
3. **Transport Settings**: Configure stations and timing preferences
4. **Import/Export**: Backup and restore configuration settings
### Transport Integration
When transport is configured, the application automatically:
- Calculates optimal departure times for work commutes
- Displays train connections and travel duration
- Applies safety buffers for reliable planning
- Shows both morning and afternoon journey options
## Technical Architecture
- **Frontend**: Blazor WebAssembly (.NET 9.0)
- **Backend**: ASP.NET Core Web API (.NET 9.0)
- **Authentication**: Google OAuth 2.0
- **Export**: QuestPDF for PDF generation, custom ICS implementation
- **Transport**: Swiss Public Transport API integration
- **Configuration**: JSON-based with external persistence support
## Documentation ## Documentation
- [📚 Authentication Setup Guide](authentication-setup.md) - Complete Google OAuth configuration - [Authentication Setup Guide](authentication-setup.md) — Google OAuth configuration
- [🐳 Docker Configuration Guide](DOCKER_CONFIG.md) - Containerization and deployment - [Docker Configuration Guide](DOCKER_CONFIG.md) — Containerized deployment
- [📄 License](LICENSE) - Apache 2.0 License
## Contributing
This project follows standard .NET development practices:
- Uses .NET 9.0 target framework
- Implements nullable reference types
- Follows established architectural patterns
- Includes comprehensive configuration options
## Support
For issues, questions, or feature requests, please use the GitHub issue tracker.
## License ## License
This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details. Apache License 2.0 — see the [LICENSE](LICENSE) file for details.

View file

@ -124,6 +124,14 @@ app.UseHttpsRedirection();
app.UseBlazorFrameworkFiles(); app.UseBlazorFrameworkFiles();
app.UseStaticFiles(); app.UseStaticFiles();
var iconsPath = Path.Combine(Directory.GetCurrentDirectory(), "config", "icons");
Directory.CreateDirectory(iconsPath);
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new Microsoft.Extensions.FileProviders.PhysicalFileProvider(iconsPath),
RequestPath = "/icons"
});
app.UseRouting(); app.UseRouting();
app.UseAuthentication(); app.UseAuthentication();

View file

@ -0,0 +1,174 @@
# Configurable Icons from Docker Share Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Serve shift icons from `config/icons/` on the Docker share instead of bundling them in the repository.
**Architecture:** Add a second `UseStaticFiles` middleware in `Server/Program.cs` with a `PhysicalFileProvider` pointing to `config/icons/`, served at the `/icons` URL path. Remove all PNG icon files and their `.csproj` entries from the repo. No frontend changes needed — `<img src="icons/filename.png">` continues to work unchanged.
**Tech Stack:** ASP.NET Core static files middleware, `Microsoft.Extensions.FileProviders.PhysicalFileProvider` (already in the ASP.NET Core SDK, no new package required)
---
## Files Changed
| Action | File | What changes |
|--------|------|--------------|
| Modify | `Server/Program.cs` | Add second `UseStaticFiles` for `config/icons/` |
| Modify | `Client/ShiftScheduler.Client.csproj` | Remove all `<Content Update="wwwroot\icons\*">` and `<Content Update="wwwroot\break-icon.png">` entries |
| Delete | `Client/wwwroot/icons/` | Remove entire directory and all PNGs |
| Delete | `Client/wwwroot/break-icon.png` (if present) | Remove stray root-level copy |
---
### Task 1: Add static file middleware for `config/icons/`
**Files:**
- Modify: `Server/Program.cs:125` (after `app.UseStaticFiles();`)
- [ ] **Step 1: Add the icons static file middleware**
In `Server/Program.cs`, replace:
```csharp
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
```
with:
```csharp
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
var iconsPath = Path.Combine(Directory.GetCurrentDirectory(), "config", "icons");
Directory.CreateDirectory(iconsPath);
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new Microsoft.Extensions.FileProviders.PhysicalFileProvider(iconsPath),
RequestPath = "/icons"
});
```
- [ ] **Step 2: Verify the app builds**
```bash
dotnet build
```
Expected: Build succeeded with 0 errors (pre-existing warnings about QuestPDF and null reference are OK).
- [ ] **Step 3: Commit**
```bash
git checkout -b feature/configurable-icons
git add Server/Program.cs
git commit -m "feat: serve icons from config/icons/ Docker share via static file middleware"
```
---
### Task 2: Remove icon files and clean up the .csproj
**Files:**
- Modify: `Client/ShiftScheduler.Client.csproj` — remove `<ItemGroup>` with icon `<Content>` entries
- Delete: `Client/wwwroot/icons/` directory
- Delete: `Client/wwwroot/break-icon.png` (stray root-level copy listed in .csproj)
- [ ] **Step 1: Remove the entire icons ItemGroup from the .csproj**
In `Client/ShiftScheduler.Client.csproj`, remove the entire `<ItemGroup>` block that contains the icon entries (lines 14–48 in the current file), so the file becomes:
```xml
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.8" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Shared\ShiftScheduler.Shared.csproj" />
<ProjectReference Include="..\Services\ShiftScheduler.Services.csproj" />
</ItemGroup>
</Project>
```
- [ ] **Step 2: Delete the icon files from the repo**
```bash
rm -rf Client/wwwroot/icons
rm -f Client/wwwroot/break-icon.png
```
- [ ] **Step 3: Verify the app still builds**
```bash
dotnet build
```
Expected: Build succeeded with 0 errors.
- [ ] **Step 4: Commit**
```bash
git add Client/ShiftScheduler.Client.csproj
git add -u Client/wwwroot/
git commit -m "chore: remove bundled icon files from repo — icons now served from Docker share"
```
---
### Task 3: Smoke test the icons endpoint
This task verifies the middleware works end-to-end before the branch is merged.
- [ ] **Step 1: Copy a test icon to the config/icons directory**
```bash
mkdir -p config/icons
# Copy any PNG you have on hand, e.g. the app favicon
cp Client/wwwroot/favicon.png config/icons/test.png
```
- [ ] **Step 2: Start the server**
```bash
cd Server && dotnet run
```
Expected: App starts at `http://localhost:5000`.
- [ ] **Step 3: Verify the icon is served**
```bash
curl -o /dev/null -w "%{http_code}" http://localhost:5000/icons/test.png
```
Expected output: `200`
- [ ] **Step 4: Verify a missing icon returns 404**
```bash
curl -o /dev/null -w "%{http_code}" http://localhost:5000/icons/nonexistent.png
```
Expected output: `404`
- [ ] **Step 5: Stop the server and clean up the test file**
```bash
rm config/icons/test.png
```
- [ ] **Step 6: Merge to main**
```bash
cd ..
git checkout main
git merge --squash feature/configurable-icons
git commit -m "feat: serve icons from config/icons/ Docker share"
git branch -d feature/configurable-icons
```

View file

@ -0,0 +1,59 @@
# Configurable Icons from Docker Share
**Date:** 2026-04-14
**Status:** Approved
## Problem
Icons for shifts are currently PNG files bundled in the repository under `Client/wwwroot/icons/`. This means adding or changing an icon requires a code change and a redeployment. The user wants icons to be configurable at runtime, placed on the same Docker-mounted volume as monthly schedule files and other configuration.
## Solution
Serve icon files from `config/icons/` (a subfolder of the existing Docker share) using ASP.NET Core's static file middleware with a `PhysicalFileProvider`. No frontend changes are required — the URL path `/icons/filename.png` stays identical.
## Architecture
### Server change (`Server/Program.cs`)
Add a second `UseStaticFiles` call after the existing one:
```csharp
var iconsPath = Path.Combine(Directory.GetCurrentDirectory(), "config", "icons");
Directory.CreateDirectory(iconsPath);
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(iconsPath),
RequestPath = "/icons"
});
```
- `config/icons/` is created automatically on first run if absent.
- Files placed there are served at `/icons/{filename}` immediately, no restart needed.
- Built-in ASP.NET Core static file serving provides ETags, cache headers, and range request support.
### Cleanup
- Delete `Client/wwwroot/icons/` and all PNG files within it.
- Remove all `<Content Update="wwwroot\icons\*.png">` entries from `Client/ShiftScheduler.Client.csproj`.
### No changes needed
- `Shift.IsPngIcon` computed property
- `Shift.Icon` field
- `<img src="icons\@shift.Icon">` in `Index.razor`
- `ConfigurationDialog.razor`
- Any other frontend or shared model code
## Data Flow
1. User places `myicon.png` in `config/icons/` on the Docker share.
2. In `appsettings.json` (or `config/shifts.json`), the shift's `Icon` field is set to `myicon.png`.
3. The Blazor client fetches shift config from `GET /api/shift/shifts`.
4. `Shift.IsPngIcon` returns `true`, and the UI renders `<img src="icons/myicon.png">`.
5. The browser GETs `/icons/myicon.png` — served by the `PhysicalFileProvider` from `config/icons/myicon.png`.
## Out of Scope
- Emoji icons are unaffected.
- No upload UI for icons — files are placed on the share directly (consistent with how schedule files work).
- No fallback to repo-bundled icons (repo icons are removed entirely).