feat(scheduling,admin,today): week-range queries, file size limits, mobile nav UX

- Expand exceptional application query to Monday of start week
- Add 20MB file size limit with visual feedback in ImageUpload
- Reorder admin nav links, add burger badge, slide transition + backdrop
- Fetch week-range working/available hours, add closing time indicator
- Skip lunch protection for days <= 5h via shouldApplyLunchProtection
- Extract formatDateISO to shared utils
This commit is contained in:
2026-06-03 10:22:16 +01:00
parent b1847b4cfc
commit 169d7dc6e3
6 changed files with 150 additions and 27 deletions
@@ -14,6 +14,21 @@
let uploadStatus = $state<Record<string, string>>({});
let uploadResults = $state<{ name: string; url?: string; error?: string }[]>([]);
const MAX_FILE_SIZE = 20 * 1024 * 1024; // 20MB
function formatFileSize(bytes: number): string {
if (bytes >= 1024 * 1024) {
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
}
return (bytes / 1024).toFixed(1) + ' KB';
}
function isFileTooBig(file: File): boolean {
return file.size > MAX_FILE_SIZE;
}
let hasOversizedFiles = $derived(uploadFiles.some(isFileTooBig));
function handleFilesDropped(files: File[]) {
uploadFiles = files;
generatePreviews(files);
@@ -505,7 +520,7 @@
<div class="flex flex-col items-start justify-between gap-2 sm:flex-row sm:items-center">
<div>
<Card.Title>Drop or Select Files</Card.Title>
<Card.Description>Supported: JPEG, PNG, WebP, and other image formats</Card.Description>
<Card.Description>HEIC, AVIF, WebP, PNG, JPEG and more. Maximum 20MB per file</Card.Description>
</div>
</div>
</Card.Header>
@@ -529,7 +544,7 @@
<line x1="12" y1="3" x2="12" y2="15" />
</svg>
<p class="text-sm font-medium text-gray-700">Drop images here or click to browse</p>
<p class="mt-1 text-xs text-gray-500">Supports AVIF, PNG, JPG, WebP and other formats</p>
<p class="mt-1 text-xs text-gray-500">HEIC, AVIF, WebP, PNG, JPEG, GIF &amp; more &mdash; max 20MB per file</p>
</div>
</FileDropZone>
@@ -555,8 +570,11 @@
<div class="space-y-2">
{#each uploadFiles as f (f.name)}
{@const preview = filePreviews.find((p) => p.file === f)?.preview}
{@const oversized = isFileTooBig(f)}
<div
class="flex items-center justify-between rounded-lg border border-gray-200 bg-white p-3 sm:p-4"
class="flex items-center justify-between rounded-lg border bg-white p-3 sm:p-4"
class:border-red-300={oversized}
class:border-gray-200={!oversized}
>
<div class="flex min-w-0 flex-1 items-center gap-3">
<div class="flex-shrink-0">
@@ -580,9 +598,16 @@
{/if}
</div>
<div class="min-w-0 flex-1">
<p class="truncate text-sm font-medium text-gray-900">{f.name}</p>
<p class="truncate text-sm font-medium" class:text-gray-900={!oversized} class:text-red-900={oversized}>
{f.name}
</p>
<div class="flex items-center gap-2">
<p class="text-xs text-gray-500">{(f.size / 1024).toFixed(1)} KB</p>
<p class="text-xs" class:text-gray-500={!oversized} class:text-red-600={oversized}>
{formatFileSize(f.size)}
{#if oversized}
&mdash; exceeds 20MB limit
{/if}
</p>
{#if uploading && uploadStatus[f.name]}
<span
class="inline-flex items-center gap-1.5 text-xs font-medium text-blue-600"
@@ -625,10 +650,10 @@
</div>
{/each}
</div>
</div>
{/if}
</div>
{/if}
<!-- Upload Results Section -->
<!-- Upload Results Section -->
{#if uploadResults.length > 0}
<div class="border-t pt-6">
<h3 class="mb-4 font-semibold text-gray-900">Upload Results</h3>
@@ -780,7 +805,7 @@
<!-- Action Buttons -->
<div class="border-t pt-6">
<div class="flex justify-end">
<Button onclick={uploadOneOrMany} disabled={!uploadFiles.length || uploading}>
<Button onclick={uploadOneOrMany} disabled={!uploadFiles.length || uploading || hasOversizedFiles}>
{#if uploading}
<svg
class="mr-2 h-4 w-4 animate-spin"