# Implementation Plan: Issue #52 - FileLinkService + FileExplorer component

## Summary
Implement a FileLinkService for managing Nextcloud file folder links per member/Lager, with configurable path patterns, auto-create toggle, and a FileExplorer Vue component that displays folder contents inline on the member detail page "Dateien" tab. This integrates with Nextcloud's `IRootFolder` for file system operations.

## Files to Create/Modify

### New Files
1. **`lib/Service/FileLinkService.php`** - Core service for member/Lager folder path computation, folder listing, and auto-creation via `IRootFolder`
2. **`lib/Controller/FileController.php`** - REST controller for file browsing and settings endpoints
3. **`src/components/FileExplorer.vue`** - Vue 3 inline file browser component for member detail "Dateien" tab
4. **`src/stores/files.js`** - Pinia store for file browsing state
5. **`tests/Unit/FileLinkServiceTest.php`** - Unit tests for path computation and validation

### Modified Files
6. **`appinfo/routes.php`** - Add file-related API routes
7. **`src/views/MemberDetail.vue`** - Wire up "Dateien" tab to use FileExplorer component instead of placeholder

## Step-by-Step Plan

### Step 1: Create FileLinkService.php
- Constructor: inject `IRootFolder`, `IConfig` (for app settings), `LoggerInterface`
- `getMemberFolderPath(Member $member): string` - compute path from configurable base + pattern (e.g., `/Verein/Mitglieder/{Nachname}_{Vorname}/`)
- `ensureMemberFolder(Member $member): bool` - create folder if auto-create enabled and folder doesn't exist; return true if folder exists after call
- `listFolderContents(string $path): array` - list files in a folder via `IRootFolder->getUserFolder()`, return array of `[name, size, mtime, mimetype, path]`
- `getMemberFiles(Member $member): array` - convenience: compute path + list contents
- `getLagerFolderPath(Lager $lager): string` - same pattern for Lager folders
- Settings helpers: `getBasePath(): string`, `getSubfolderPattern(): string`, `isAutoCreateEnabled(): bool`, `getLagerBasePath(): string`
- Settings are stored via `IConfig::setAppValue/getAppValue` with keys:
  - `file_base_path` (default: `/Verein/Mitglieder/`)
  - `file_subfolder_pattern` (default: `{Nachname}_{Vorname}`)
  - `file_auto_create` (default: `false`)
  - `file_lager_base_path` (default: `/Verein/Lager/`)

### Step 2: Create FileController.php
Following exact pattern from LagerController:
- Constructor: inject `FileLinkService`, `IRequest`, `LoggerInterface`
- `getSettings()` - GET `/api/v1/files/settings` - return current file settings
- `updateSettings()` - PUT `/api/v1/files/settings` - update file settings (admin only)
- `memberFiles(int $memberId)` - GET `/api/v1/members/{memberId}/files` - list member folder contents
- `ensureFolder(int $memberId)` - POST `/api/v1/members/{memberId}/files/ensure-folder` - create member folder if auto-create
- `lagerFiles(int $lagerId)` - GET `/api/v1/lager/{lagerId}/files/browse` - browse Lager folder

### Step 3: Add Routes to appinfo/routes.php
Add file-related routes in a new section:
```php
// -- Files integration ---
['name' => 'file#getSettings',    'url' => '/api/v1/files/settings',                      'verb' => 'GET'],
['name' => 'file#updateSettings', 'url' => '/api/v1/files/settings',                      'verb' => 'PUT'],
['name' => 'file#memberFiles',    'url' => '/api/v1/members/{memberId}/files',             'verb' => 'GET'],
['name' => 'file#ensureFolder',   'url' => '/api/v1/members/{memberId}/files/ensure-folder', 'verb' => 'POST'],
['name' => 'file#lagerFiles',     'url' => '/api/v1/lager/{lagerId}/files/browse',         'verb' => 'GET'],
```

### Step 4: Create Pinia store (src/stores/files.js)
- State: `files`, `loading`, `error`, `folderExists`, `settings`
- Actions: `fetchMemberFiles(memberId)`, `ensureMemberFolder(memberId)`, `fetchSettings()`, `updateSettings(settings)`
- Uses axios pattern consistent with other stores (e.g., members.js)

### Step 5: Create FileExplorer.vue component
- Props: `memberId` (Number, required)
- On mount: fetch member files via store
- Display: table with columns: Dateiname, Groesse, Geaendert
- Click file: open in NC Files (new tab via `/apps/files/?dir=...&openfile=...`)
- "In Nextcloud Files oeffnen" button: navigate to folder in NC Files
- Warning banner when folder doesn't exist + "Ordner erstellen" button (calls ensureFolder)
- Loading spinner while fetching
- Empty state when folder exists but is empty
- Uses `@nextcloud/vue` NcButton, NcLoadingIcon, NcEmptyContent

### Step 6: Wire MemberDetail.vue "Dateien" tab
- Import FileExplorer component
- In the tab content section, when `activeTab === 'files'`, render `<FileExplorer :member-id="Number(id)" />`
- Remove the placeholder "Wird in einem spaeteren Milestone implementiert" for the files tab

### Step 7: Create Unit Test
- Test `getMemberFolderPath()` with various patterns
- Test path pattern substitution
- Test settings defaults

## Acceptance Criteria Verification Checklist
1. [ ] FileLinkService.php exists with getMemberFolderPath, ensureMemberFolder, listFolderContents, getMemberFiles, getLagerFolderPath methods
2. [ ] FileController.php exists with getSettings, updateSettings, memberFiles, ensureFolder, lagerFiles endpoints
3. [ ] Routes added to appinfo/routes.php for all 5 file endpoints
4. [ ] FileExplorer.vue component renders file list with name, size, modified date columns
5. [ ] FileExplorer.vue shows warning state when folder doesn't exist
6. [ ] FileExplorer.vue has "In Nextcloud Files oeffnen" button that generates correct NC Files URL
7. [ ] MemberDetail.vue "Dateien" tab renders FileExplorer instead of placeholder
8. [ ] Settings configurable: base path, subfolder pattern, auto-create toggle, Lager base path
9. [ ] Pinia store (files.js) manages state for file browsing
10. [ ] Unit test validates path computation logic
