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>
|
|
@ -11,40 +11,4 @@
|
|||
<ProjectReference Include="..\Services\ShiftScheduler.Services.csproj" />
|
||||
</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>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 3 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 186 B |
207
README.md
|
|
@ -1,75 +1,59 @@
|
|||
# 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
|
||||
|
||||
ShiftScheduler is a web-based shift management system that allows users to:
|
||||
- Plan and visualize work shifts using an intuitive calendar interface
|
||||
- Export schedules to industry-standard formats (ICS and PDF)
|
||||
- Calculate optimal transport connections for work commutes
|
||||
- Manage multiple shift types with customizable icons and time periods
|
||||
- Plan and visualize work shifts using a monthly calendar interface
|
||||
- Export schedules to ICS and PDF formats
|
||||
- Sync shifts directly to Google Calendar or Nextcloud Calendar
|
||||
- View optimal transport connections for work commutes
|
||||
- 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
|
||||
|
||||
### 📅 **Calendar-Based Shift Management**
|
||||
- Interactive monthly calendar view with easy shift selection
|
||||
- Switch between current and next month
|
||||
- Visual shift indicators with customizable icons and colors
|
||||
- Responsive design for desktop and mobile devices
|
||||
### Calendar-Based Shift Management
|
||||
- Monthly calendar view with day-by-day shift assignment
|
||||
- Toggle between current and next month
|
||||
- Mobile-friendly week layout alongside the desktop grid view
|
||||
|
||||
### 🔄 **Export Capabilities**
|
||||
- **ICS Export**: Import schedules directly into Google Calendar, Outlook, or any calendar application
|
||||
- **PDF Export**: Generate printable shift schedules with professional formatting
|
||||
- **Google Calendar Sync**: Direct synchronization with Google Calendar (NEW!)
|
||||
- Automatic calendar selection for multiple calendars
|
||||
- Smart event management (updates existing app-created events)
|
||||
- Respects existing calendar events from other sources
|
||||
- One-click export functionality
|
||||
### Export & Sync
|
||||
- **ICS Export**: Download a calendar file compatible with any calendar application
|
||||
- **PDF Export**: Generate a printable monthly schedule
|
||||
- **Google Calendar Sync**: Direct sync to a selected Google Calendar; existing app-managed events are updated, other events are untouched
|
||||
- **Nextcloud Calendar Sync**: CalDAV-based sync to a selected Nextcloud calendar
|
||||
|
||||
### 🚆 **Transport Integration**
|
||||
- Automatic calculation of travel times between configurable stations
|
||||
- Integration with Swiss public transport API (transport.opendata.ch)
|
||||
- Safety buffer calculations for reliable commute planning
|
||||
- Display of optimal departure and arrival times
|
||||
### Transport Integration
|
||||
- Calculates optimal departure and arrival times for work commutes
|
||||
- Integrates with the Swiss public transport API (`transport.opendata.ch`)
|
||||
- Displays morning and afternoon connections alongside each shift
|
||||
- Configurable safety buffers and break durations
|
||||
|
||||
### 🔐 **Secure Authentication**
|
||||
- Google OAuth integration for secure access
|
||||
- Configurable authorized email addresses
|
||||
- Session management with proper login/logout functionality
|
||||
### Authentication
|
||||
- Google OAuth 2.0 with a configurable list of authorized email addresses
|
||||
|
||||
### ⚙️ **Flexible Configuration**
|
||||
- **Shift Types**: Fully customizable shift definitions with:
|
||||
- Custom names and icons (emoji or PNG images)
|
||||
- Morning and afternoon time periods
|
||||
- Flexible time format support
|
||||
- **Transport Settings**: Configurable start/end stations, API parameters, and timing buffers
|
||||
- **Persistent Configuration**: Settings survive application restarts and container rebuilds
|
||||
### Configuration
|
||||
- Shift definitions (name, emoji icon, morning/afternoon time ranges) managed via the in-app configuration dialog
|
||||
- Transport settings (stations, API URL, timing buffers) configurable in-app
|
||||
- Configuration can be exported and imported as JSON for backup
|
||||
|
||||
### 🐳 **Docker Ready**
|
||||
- Container-friendly with persistent external configuration
|
||||
- Easy deployment with Docker Compose
|
||||
- Configuration backup and version control support
|
||||
## Default Shift Types
|
||||
|
||||
## 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 |
|
||||
|------|------|--------------|----------------|-------------|
|
||||
| ⚫ | 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 configurable through the in-app settings dialog.
|
||||
|
||||
*All shift types are fully configurable through the application interface.*
|
||||
## Configuration
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### Authentication Settings
|
||||
### Authentication (`Server/appsettings.json`)
|
||||
```json
|
||||
{
|
||||
"Authentication": {
|
||||
|
|
@ -78,14 +62,24 @@ Perfect for individuals or small teams who need to organize shift work, track sc
|
|||
"ClientSecret": "your-google-client-secret"
|
||||
},
|
||||
"AuthorizedEmails": [
|
||||
"user1@gmail.com",
|
||||
"user2@example.com"
|
||||
"user@gmail.com"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Transport Configuration
|
||||
### Nextcloud CalDAV
|
||||
```json
|
||||
{
|
||||
"Nextcloud": {
|
||||
"BaseUrl": "https://your-nextcloud-instance.example.com",
|
||||
"Username": "your-username",
|
||||
"AppPassword": "your-app-password"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Transport
|
||||
```json
|
||||
{
|
||||
"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
|
||||
|
||||
### Prerequisites
|
||||
- .NET 9.0 SDK
|
||||
- Modern web browser
|
||||
- Google Cloud Console account (for authentication)
|
||||
- Google Cloud Console account (for OAuth)
|
||||
|
||||
### Quick Start
|
||||
|
||||
|
|
@ -135,10 +114,9 @@ Perfect for individuals or small teams who need to organize shift work, track sc
|
|||
dotnet restore
|
||||
```
|
||||
|
||||
3. **Configure authentication** (see [Authentication Setup Guide](authentication-setup.md))
|
||||
- Create Google OAuth application
|
||||
- Update `Server/appsettings.json` with your credentials
|
||||
- Add authorized email addresses
|
||||
3. **Configure authentication**
|
||||
- See [Authentication Setup Guide](authentication-setup.md)
|
||||
- Update `Server/appsettings.json` with your Google OAuth credentials and authorized emails
|
||||
|
||||
4. **Build and run**
|
||||
```bash
|
||||
|
|
@ -147,84 +125,15 @@ Perfect for individuals or small teams who need to organize shift work, track sc
|
|||
dotnet run
|
||||
```
|
||||
|
||||
5. **Access the application**
|
||||
5. **Open the app**
|
||||
- Navigate to `http://localhost:5000`
|
||||
- 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
|
||||
|
||||
- [📚 Authentication Setup Guide](authentication-setup.md) - Complete Google OAuth configuration
|
||||
- [🐳 Docker Configuration Guide](DOCKER_CONFIG.md) - Containerization and 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.
|
||||
- [Authentication Setup Guide](authentication-setup.md) — Google OAuth configuration
|
||||
- [Docker Configuration Guide](DOCKER_CONFIG.md) — Containerized deployment
|
||||
|
||||
## 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.
|
||||
|
|
|
|||
|
|
@ -124,6 +124,14 @@ app.UseHttpsRedirection();
|
|||
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"
|
||||
});
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseAuthentication();
|
||||
|
|
|
|||
174
docs/superpowers/plans/2026-04-14-configurable-icons.md
Normal 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
|
||||
```
|
||||
|
|
@ -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).
|
||||